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.
- package/README.md +26 -0
- package/cli.js +2 -2
- package/out/404/index.html +15 -0
- package/out/404.html +15 -0
- package/out/__next.__PAGE__.txt +9 -0
- package/out/__next._full.txt +18 -0
- package/out/__next._head.txt +5 -0
- package/out/__next._index.txt +6 -0
- package/out/__next._tree.txt +2 -0
- package/out/_next/static/0h4f4QFk_KC9FlSRfQACk/_buildManifest.js +11 -0
- package/out/_next/static/0h4f4QFk_KC9FlSRfQACk/_clientMiddlewareManifest.js +1 -0
- package/out/_next/static/0h4f4QFk_KC9FlSRfQACk/_ssgManifest.js +1 -0
- package/out/_next/static/chunks/00l-yd3t8dvwz.js +5 -0
- package/out/_next/static/chunks/03k8t3tgym~8~.js +1 -0
- package/out/_next/static/chunks/03~yq9q893hmn.js +1 -0
- package/out/_next/static/chunks/09vfh8lfuacc0.css +1 -0
- package/out/_next/static/chunks/0bogtdbh.dcu1.js +1 -0
- package/out/_next/static/chunks/0dbhjjzl8qfwv.js +1 -0
- package/out/_next/static/chunks/0f73psqhr8dre.css +1 -0
- package/out/_next/static/chunks/0fbi7z4_.4j1j.js +1 -0
- package/out/_next/static/chunks/0ht900cau6_ur.js +31 -0
- package/out/_next/static/chunks/0ohm.ia-4ec60.js +1 -0
- package/out/_next/static/chunks/0u5ydb-f0.vxl.js +1 -0
- package/out/_next/static/chunks/14t2m1on-s5v~.js +1 -0
- package/out/_next/static/chunks/turbopack-076ce9exut_h3.js +1 -0
- package/out/_not-found/__next._full.txt +16 -0
- package/out/_not-found/__next._head.txt +5 -0
- package/out/_not-found/__next._index.txt +6 -0
- package/out/_not-found/__next._not-found/__PAGE__.txt +5 -0
- package/out/_not-found/__next._not-found.txt +5 -0
- package/out/_not-found/__next._tree.txt +2 -0
- package/out/_not-found/index.html +15 -0
- package/out/_not-found/index.txt +16 -0
- package/out/app.css +1535 -0
- package/out/bundle.js +107 -0
- package/out/bundle.js.map +7 -0
- package/out/chat/__next._full.txt +19 -0
- package/out/chat/__next._head.txt +5 -0
- package/out/chat/__next._index.txt +6 -0
- package/out/chat/__next._tree.txt +3 -0
- package/out/chat/__next.chat/__PAGE__.txt +9 -0
- package/out/chat/__next.chat.txt +5 -0
- package/out/chat/index.html +15 -0
- package/out/chat/index.txt +19 -0
- package/out/chat-page.js +112 -0
- package/out/chat.css +378 -0
- package/out/favicon.ico +0 -0
- package/out/index.html +15 -0
- package/out/index.js +148 -0
- package/out/index.txt +18 -0
- package/package.json +11 -6
- package/public/app.css +20 -4
- package/public/bundle.js +1 -1
- package/public/bundle.js.map +7 -0
- package/public/chat-page.js +112 -0
- package/public/chat.css +378 -0
- package/public/index.js +148 -0
- package/server.js +188 -6
- package/src/config.js +12 -1
- package/src/core/cid.js +7 -3
- package/src/index.js +475 -7
- package/src/utils/api.js +6 -0
- package/build.mjs +0 -40
- package/public/app.jsx +0 -1543
- package/public/bundle.css +0 -1
- package/public/error-boundary.jsx +0 -50
- package/public/index.html +0 -16
- 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(
|
|
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
|
-
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|