most-box 0.2.0 → 0.2.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.
- package/README.md +3 -3
- package/out/admin/index.html +0 -0
- package/out/app/index.html +0 -0
- package/out/assets/{AppShell-CQhg6DJU.js → AppShell-OiOEqXPr.js} +1 -1
- package/out/assets/ChatUi-Cif5LRF3.js +1 -0
- package/out/assets/CopyButton-Dm7krgbq.js +1 -0
- package/out/assets/LanguageToggle-B4ZNuBCV.js +1 -0
- package/out/assets/LogoIcon-B2fFe0l1.js +1 -0
- package/out/assets/MarketingHeader-yIZuQP7m.js +1 -0
- package/out/assets/MarketingLayout-DVH0Nx7S.js +1 -0
- package/out/assets/{MarketingThemeToggle-DBaC9bjz.js → MarketingThemeToggle-qlwCZU1o.js} +1 -1
- package/out/assets/{MilkdownEditor-BqJzntYE.js → MilkdownEditor-_TGlDyA_.js} +1 -1
- package/out/assets/MoveModal-BVr4Q7-b.js +1 -0
- package/out/assets/Nav-5xeettNJ.js +1 -0
- package/out/assets/NoteSidebar-DpniUKmy.js +1 -0
- package/out/assets/OpenSidebarButton-BfgG2HIT.js +1 -0
- package/out/assets/PemBlock-CxwIepth.js +1 -0
- package/out/assets/SidebarAccount-Zg5DZblE.js +1 -0
- package/out/assets/arrow-right-CL9YSDVS.js +1 -0
- package/out/assets/channelApi-DNdJfsJ-.js +1 -0
- package/out/assets/chevron-down-CnLh_-aO.js +1 -0
- package/out/assets/{circle-alert-W0iyN4sC.js → circle-alert-oiiRDvhx.js} +1 -1
- package/out/assets/cloud-BEe2N89j.js +1 -0
- package/out/assets/code-9LB8QqxL.js +1 -0
- package/out/assets/{copy-C1MttOli.js → copy-giX4rmFJ.js} +1 -1
- package/out/assets/{download-y7SZXu6E.js → download-D0oMEYQZ.js} +1 -1
- package/out/assets/{downloadValidation-B0p9Ai_9.js → downloadValidation-Bk1VsBBo.js} +1 -1
- package/out/assets/external-link-Cm2WCUxv.js +1 -0
- package/out/assets/filePreview-BZ50vZZf.js +1 -0
- package/out/assets/game-Bvz4dspe.js +1 -0
- package/out/assets/{hard-drive-D13Qbobu.js → hard-drive-B3CQbcp2.js} +1 -1
- package/out/assets/index-BkZvz4WA.css +1 -0
- package/out/assets/index-WCK14Vja.js +34 -0
- package/out/assets/index.lazy-5Q6GuMNT.js +1 -0
- package/out/assets/index.lazy-5jq6EFXa.js +3 -0
- package/out/assets/index.lazy-7n1Q-NrA.js +3 -0
- package/out/assets/index.lazy-BFnOyQFj.js +1 -0
- package/out/assets/index.lazy-B_oPp6qK.js +1 -0
- package/out/assets/index.lazy-BvY50KVz.js +1 -0
- package/out/assets/index.lazy-C0Kn_amZ.js +1 -0
- package/out/assets/index.lazy-C3cek3Gn.js +1 -0
- package/out/assets/index.lazy-CLpPkdy1.js +1 -0
- package/out/assets/index.lazy-Cpr1kApf.js +2 -0
- package/out/assets/index.lazy-CuwLZiUK.js +1 -0
- package/out/assets/index.lazy-DDc3Ylgf.js +2 -0
- package/out/assets/index.lazy-Dg3aqOss.js +1 -0
- package/out/assets/{key-round-CZniN9lv.js → key-round-CzuljhND.js} +1 -1
- package/out/assets/{lock-D5OSNhep.js → lock-D2NhNoJW.js} +1 -1
- package/out/assets/message-square-DwBq_Go5.js +1 -0
- package/out/assets/mp-Bln2MB9G.js +1 -0
- package/out/assets/{music-CbUskKgg.js → music-CB73K5Gz.js} +1 -1
- package/out/assets/{notebook-pen-DqKDQ6MJ.js → notebook-pen-Up7r5zoI.js} +1 -1
- package/out/assets/play-OszVgROb.js +1 -0
- package/out/assets/plus-BbxQG_Ai.js +1 -0
- package/out/assets/{save-DkH1n_Ov.js → save-CiqyiifY.js} +1 -1
- package/out/assets/search-gqAPOsgS.js +1 -0
- package/out/assets/{send-Cl6NtD2T.js → send-vwCWsZGP.js} +1 -1
- package/out/assets/shield-check-CxWxsNLc.js +1 -0
- package/out/assets/{trash-2-BBjpgK_f.js → trash-2-DNGr8IgF.js} +1 -1
- package/out/assets/{triangle-alert-l98G8u9O.js → triangle-alert-B_1BlX1b.js} +1 -1
- package/out/assets/{upload-ByP6Ydde.js → upload-Dxl7GUzb.js} +1 -1
- package/out/assets/useChannelMessages-7bYKXU_R.js +3 -0
- package/out/assets/useGameRoom-DqA1mkfk.js +1 -0
- package/out/assets/wallet-DlkawdPJ.js +1 -0
- package/out/assets/{wifi-Bm4biAjc.js → wifi-sBOKcPFM.js} +1 -1
- package/out/avatars/default/LICENSE.md +7 -0
- package/out/avatars/default/dusk.svg +100 -0
- package/out/avatars/default/ember.svg +55 -0
- package/out/avatars/default/mint.svg +29 -0
- package/out/avatars/default/ocean.svg +25 -0
- package/out/avatars/default/sage.svg +34 -0
- package/out/avatars/default/violet.svg +60 -0
- package/out/chat/index.html +0 -0
- package/out/chat/join/index.html +0 -0
- package/out/demo/index.html +0 -0
- package/out/download/index.html +4 -3
- 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 +4 -3
- package/out/note/index.html +0 -0
- package/out/ping/index.html +4 -3
- package/out/profile/index.html +0 -0
- package/out/web3/index.html +0 -0
- package/package.json +1 -1
- package/public/avatars/default/LICENSE.md +7 -0
- package/public/avatars/default/dusk.svg +100 -0
- package/public/avatars/default/ember.svg +55 -0
- package/public/avatars/default/mint.svg +29 -0
- package/public/avatars/default/ocean.svg +25 -0
- package/public/avatars/default/sage.svg +34 -0
- package/public/avatars/default/violet.svg +60 -0
- package/server/src/core/channelIdentity.js +2 -13
- package/server/src/http/app.js +1 -3
- package/server/src/index.js +286 -249
- package/server/src/utils/avatar.js +44 -3
- package/out/assets/ChatUi-BepWs-ZU.js +0 -1
- package/out/assets/LanguageToggle-CtzCCAYv.js +0 -1
- package/out/assets/LogoIcon-Dxto3Sb4.js +0 -1
- package/out/assets/MarketingLayout-BQw0IS2i.js +0 -1
- package/out/assets/MoveModal-4D9n11Kw.js +0 -1
- package/out/assets/Nav-9MDdvgNs.js +0 -1
- package/out/assets/NoteSidebar-C-rIt32H.js +0 -1
- package/out/assets/OpenSidebarButton-Dd0JmKuE.js +0 -1
- package/out/assets/PemBlock-C8dEIzu-.js +0 -1
- package/out/assets/SidebarAccount-ClS-N0lq.js +0 -1
- package/out/assets/arrow-right-urE9Rd7j.js +0 -1
- package/out/assets/channelApi-BwQU0-h1.js +0 -1
- package/out/assets/check-DUNsD2t6.js +0 -1
- package/out/assets/chevron-down-D6mpsfv4.js +0 -1
- package/out/assets/cloud-BMyOoC2x.js +0 -1
- package/out/assets/code-B1Cb_Icm.js +0 -1
- package/out/assets/filePreview-UI9NH34f.js +0 -1
- package/out/assets/game-CdU3xnZo.js +0 -1
- package/out/assets/image-DJCA16l_.js +0 -1
- package/out/assets/index-BdaFEQG-.css +0 -1
- package/out/assets/index-QxXZzOUL.js +0 -33
- package/out/assets/index.lazy-BBTTFanX.js +0 -1
- package/out/assets/index.lazy-BG4ZylHD.js +0 -2
- package/out/assets/index.lazy-Bi-6ZXZX.js +0 -1
- package/out/assets/index.lazy-BixWVr0B.js +0 -1
- package/out/assets/index.lazy-BjFwNYy5.js +0 -3
- package/out/assets/index.lazy-C8EIQsXY.js +0 -2
- package/out/assets/index.lazy-CarNe2uu.js +0 -1
- package/out/assets/index.lazy-DEuGu3H3.js +0 -1
- package/out/assets/index.lazy-GPyILCA7.js +0 -3
- package/out/assets/index.lazy-I8ofndXl.js +0 -1
- package/out/assets/index.lazy-TxhWsA7y.js +0 -1
- package/out/assets/index.lazy-azfky8k7.js +0 -1
- package/out/assets/log-out-B6phyZ5z.js +0 -1
- package/out/assets/play-BIl8q9eU.js +0 -1
- package/out/assets/plus-BxxbpH6Q.js +0 -1
- package/out/assets/search-BQi5Z0E-.js +0 -1
- package/out/assets/useChannelMessages-BgbYfF2c.js +0 -3
- package/out/assets/useGameRoom-DPmweWwe.js +0 -1
- package/out/assets/wallet-c7zIhNSM.js +0 -1
package/server/src/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import b4a from 'b4a'
|
|
|
16
16
|
import crypto from 'node:crypto'
|
|
17
17
|
import fs from 'node:fs'
|
|
18
18
|
import path from 'node:path'
|
|
19
|
+
import { Duplex } from 'node:stream'
|
|
19
20
|
|
|
20
21
|
import { calculateCid, parseMostLink, buildMostLink } from './core/cid.js'
|
|
21
22
|
import { normalizeChannelAttachment } from './core/channelAttachment.js'
|
|
@@ -28,11 +29,9 @@ import {
|
|
|
28
29
|
normalizeChannelDisplayName,
|
|
29
30
|
normalizeChannelAvatar,
|
|
30
31
|
normalizeChannelId,
|
|
31
|
-
createChannelFingerprint,
|
|
32
32
|
createChannelWriterId,
|
|
33
33
|
buildChannelKey,
|
|
34
34
|
normalizeChannelKey,
|
|
35
|
-
getChannelFingerprintFromKey,
|
|
36
35
|
uniqueStrings,
|
|
37
36
|
} from './core/channelIdentity.js'
|
|
38
37
|
import { getPathBaseName, getDisplayPathFolder } from './core/displayPath.js'
|
|
@@ -99,6 +98,44 @@ import {
|
|
|
99
98
|
|
|
100
99
|
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
|
101
100
|
|
|
101
|
+
function createMemoryDuplexPair() {
|
|
102
|
+
let left
|
|
103
|
+
let right
|
|
104
|
+
|
|
105
|
+
left = new Duplex({
|
|
106
|
+
read() {},
|
|
107
|
+
write(chunk, _encoding, callback) {
|
|
108
|
+
if (!right.destroyed) right.push(chunk)
|
|
109
|
+
callback()
|
|
110
|
+
},
|
|
111
|
+
final(callback) {
|
|
112
|
+
if (!right.destroyed) right.push(null)
|
|
113
|
+
callback()
|
|
114
|
+
},
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
right = new Duplex({
|
|
118
|
+
read() {},
|
|
119
|
+
write(chunk, _encoding, callback) {
|
|
120
|
+
if (!left.destroyed) left.push(chunk)
|
|
121
|
+
callback()
|
|
122
|
+
},
|
|
123
|
+
final(callback) {
|
|
124
|
+
if (!left.destroyed) left.push(null)
|
|
125
|
+
callback()
|
|
126
|
+
},
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
left.on('close', () => {
|
|
130
|
+
if (!right.destroyed) right.destroy()
|
|
131
|
+
})
|
|
132
|
+
right.on('close', () => {
|
|
133
|
+
if (!left.destroyed) left.destroy()
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
return [left, right]
|
|
137
|
+
}
|
|
138
|
+
|
|
102
139
|
export class MostBoxEngine extends EventEmitter {
|
|
103
140
|
#store = null
|
|
104
141
|
#swarm = null
|
|
@@ -123,6 +160,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
123
160
|
#channelIdDiscoveries = new Map()
|
|
124
161
|
#channelPeers = new Map()
|
|
125
162
|
#channelCandidateCache = new Map()
|
|
163
|
+
#channelStreams = new Set()
|
|
126
164
|
|
|
127
165
|
#userSyncSessions = new Map()
|
|
128
166
|
#userSyncCores = new Map()
|
|
@@ -387,6 +425,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
387
425
|
this.#channelIdDiscoveries.clear()
|
|
388
426
|
this.#channelPeers.clear()
|
|
389
427
|
this.#channelCandidateCache.clear()
|
|
428
|
+
this.#channelStreams.clear()
|
|
390
429
|
this.#channels = []
|
|
391
430
|
|
|
392
431
|
for (const [, coresMap] of this.#userSyncCores) {
|
|
@@ -1739,10 +1778,15 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1739
1778
|
|
|
1740
1779
|
const left = this.#store.replicate(true, { live: true })
|
|
1741
1780
|
const right = peerEngine.#store.replicate(false, { live: true })
|
|
1781
|
+
const [leftChat, rightChat] = createMemoryDuplexPair()
|
|
1742
1782
|
|
|
1743
1783
|
left.on('error', () => {})
|
|
1744
1784
|
right.on('error', () => {})
|
|
1785
|
+
leftChat.on('error', () => {})
|
|
1786
|
+
rightChat.on('error', () => {})
|
|
1745
1787
|
left.pipe(right).pipe(left)
|
|
1788
|
+
this.#handleChannelConnection(leftChat).catch(() => {})
|
|
1789
|
+
peerEngine.#handleChannelConnection(rightChat).catch(() => {})
|
|
1746
1790
|
this.#exchangeUserSyncSessions(peerEngine).catch(() => {})
|
|
1747
1791
|
peerEngine.#exchangeUserSyncSessions(this).catch(() => {})
|
|
1748
1792
|
|
|
@@ -1750,6 +1794,8 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
1750
1794
|
close: () => {
|
|
1751
1795
|
left.destroy()
|
|
1752
1796
|
right.destroy()
|
|
1797
|
+
leftChat.destroy()
|
|
1798
|
+
rightChat.destroy()
|
|
1753
1799
|
},
|
|
1754
1800
|
}
|
|
1755
1801
|
}
|
|
@@ -2050,7 +2096,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2050
2096
|
// --- 频道管理 ---
|
|
2051
2097
|
|
|
2052
2098
|
/**
|
|
2053
|
-
* 创建或加入频道。channelId 是用户输入的短 ID,channelKey
|
|
2099
|
+
* 创建或加入频道。channelId 是用户输入的短 ID,channelKey 与频道名一致。
|
|
2054
2100
|
* @param {string} channelIdInput - 用户可见短频道 ID
|
|
2055
2101
|
* @param {string} [type='personal'] - 频道类型
|
|
2056
2102
|
* @returns {Promise<object>}
|
|
@@ -2060,8 +2106,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2060
2106
|
const ownerAddress = normalizeOwnerAddress(options.ownerAddress)
|
|
2061
2107
|
const channelId = normalizeChannelId(channelIdInput)
|
|
2062
2108
|
const channelType = String(type || 'personal').trim() || 'personal'
|
|
2063
|
-
const selectedChannelKey = normalizeChannelKey(options.channelKey)
|
|
2064
|
-
const selectedFingerprint = String(options.fingerprint || '').trim()
|
|
2065
2109
|
|
|
2066
2110
|
if (channelId.includes('.') && channelType !== 'game') {
|
|
2067
2111
|
throw new Error('点号为系统保留,不能用于手动频道 ID')
|
|
@@ -2079,30 +2123,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2079
2123
|
throw new Error(`频道名最多 ${CHANNEL_NAME_MAX_LENGTH} 个字符`)
|
|
2080
2124
|
}
|
|
2081
2125
|
|
|
2082
|
-
if (selectedChannelKey || selectedFingerprint) {
|
|
2083
|
-
const channelKey =
|
|
2084
|
-
selectedChannelKey || buildChannelKey(channelId, selectedFingerprint)
|
|
2085
|
-
const existing = this.#channels.find(c => c.channelKey === channelKey)
|
|
2086
|
-
if (existing) {
|
|
2087
|
-
await this.#mergeChannelWriterCoreKeys(
|
|
2088
|
-
existing,
|
|
2089
|
-
options.writerCoreKeys
|
|
2090
|
-
)
|
|
2091
|
-
if (this.#upsertChannelMember(existing, options)) {
|
|
2092
|
-
existing.syncUpdatedAt = getNextSyncTimestamp(existing.syncUpdatedAt)
|
|
2093
|
-
this.#saveChannelsMetadata()
|
|
2094
|
-
this.#appendUserSyncChannelUpsertSoon(existing, ownerAddress)
|
|
2095
|
-
}
|
|
2096
|
-
return this.#formatChannelForResponse(existing, ownerAddress)
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
const candidate = this.#getCachedChannelCandidate(channelId, channelKey)
|
|
2100
|
-
if (!candidate) {
|
|
2101
|
-
throw new Error('未发现该频道候选,请重新搜索频道')
|
|
2102
|
-
}
|
|
2103
|
-
return this.#joinChannelFromCandidate(candidate, channelType, options)
|
|
2104
|
-
}
|
|
2105
|
-
|
|
2106
2126
|
const localCandidates = this.#getLocalChannelCandidates(channelId)
|
|
2107
2127
|
const remoteCandidates = options.discover
|
|
2108
2128
|
? await this.#discoverChannelCandidates(channelId, {
|
|
@@ -2114,28 +2134,26 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2114
2134
|
...remoteCandidates,
|
|
2115
2135
|
])
|
|
2116
2136
|
|
|
2117
|
-
if (candidates.length >
|
|
2118
|
-
return {
|
|
2119
|
-
conflict: true,
|
|
2120
|
-
channelId,
|
|
2121
|
-
candidates: candidates.map(candidate =>
|
|
2122
|
-
this.#formatChannelCandidateForResponse(candidate, ownerAddress)
|
|
2123
|
-
),
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
|
|
2127
|
-
if (candidates.length === 1) {
|
|
2137
|
+
if (candidates.length > 0) {
|
|
2128
2138
|
const candidate = candidates[0]
|
|
2129
2139
|
if (candidate.local) {
|
|
2130
2140
|
const existing = this.#channels.find(
|
|
2131
2141
|
channel => channel.channelKey === candidate.channelKey
|
|
2132
2142
|
)
|
|
2133
|
-
if (existing
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2143
|
+
if (existing) {
|
|
2144
|
+
const writerKeysChanged = await this.#mergeChannelWriterCoreKeys(
|
|
2145
|
+
existing,
|
|
2146
|
+
candidate.writerCoreKeys
|
|
2147
|
+
)
|
|
2148
|
+
const memberChanged = this.#upsertChannelMember(existing, options)
|
|
2149
|
+
if (writerKeysChanged || memberChanged) {
|
|
2150
|
+
existing.syncUpdatedAt = getNextSyncTimestamp(existing.syncUpdatedAt)
|
|
2151
|
+
this.#saveChannelsMetadata()
|
|
2152
|
+
this.#appendUserSyncChannelUpsertSoon(existing, ownerAddress)
|
|
2153
|
+
this.#broadcastChannelHello()
|
|
2154
|
+
}
|
|
2155
|
+
return this.#formatChannelForResponse(existing, ownerAddress)
|
|
2137
2156
|
}
|
|
2138
|
-
if (existing) return this.#formatChannelForResponse(existing, ownerAddress)
|
|
2139
2157
|
const joined = await this.#joinChannelFromCandidate(
|
|
2140
2158
|
candidate,
|
|
2141
2159
|
channelType,
|
|
@@ -2188,25 +2206,32 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2188
2206
|
? { channelKey: String(candidateInput), channelId }
|
|
2189
2207
|
: null
|
|
2190
2208
|
|
|
2191
|
-
if (!candidate?.channelKey
|
|
2209
|
+
if (!candidate?.channelKey) {
|
|
2192
2210
|
return this.createChannel(channelId, options.type || 'group', options)
|
|
2193
2211
|
}
|
|
2194
2212
|
|
|
2195
|
-
const channelKey =
|
|
2196
|
-
normalizeChannelKey(candidate.channelKey) ||
|
|
2197
|
-
buildChannelKey(channelId, String(candidate.fingerprint || '').trim())
|
|
2213
|
+
const channelKey = buildChannelKey(channelId)
|
|
2198
2214
|
const existing = this.#channels.find(c => c.channelKey === channelKey)
|
|
2199
2215
|
if (existing) {
|
|
2200
|
-
await this.#mergeChannelWriterCoreKeys(
|
|
2201
|
-
|
|
2216
|
+
const writerKeysChanged = await this.#mergeChannelWriterCoreKeys(
|
|
2217
|
+
existing,
|
|
2218
|
+
candidate.writerCoreKeys
|
|
2219
|
+
)
|
|
2220
|
+
const memberChanged = this.#upsertChannelMember(existing, options)
|
|
2221
|
+
if (writerKeysChanged || memberChanged) {
|
|
2202
2222
|
existing.syncUpdatedAt = getNextSyncTimestamp(existing.syncUpdatedAt)
|
|
2203
2223
|
this.#saveChannelsMetadata()
|
|
2204
2224
|
this.#appendUserSyncChannelUpsertSoon(existing, options.ownerAddress)
|
|
2225
|
+
this.#broadcastChannelHello()
|
|
2205
2226
|
}
|
|
2206
2227
|
return this.#formatChannelForResponse(existing, options.ownerAddress)
|
|
2207
2228
|
}
|
|
2208
2229
|
|
|
2209
|
-
const cached =
|
|
2230
|
+
const cached =
|
|
2231
|
+
this.#getCachedChannelCandidate(
|
|
2232
|
+
channelId,
|
|
2233
|
+
normalizeChannelKey(candidate.channelKey)
|
|
2234
|
+
) || this.#getCachedChannelCandidate(channelId, channelKey)
|
|
2210
2235
|
const joined = await this.#joinChannelFromCandidate(cached || candidate, 'group', {
|
|
2211
2236
|
...options,
|
|
2212
2237
|
channelKey,
|
|
@@ -2517,7 +2542,9 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2517
2542
|
author,
|
|
2518
2543
|
authorName,
|
|
2519
2544
|
content: trimmed,
|
|
2520
|
-
timestamp:
|
|
2545
|
+
timestamp: await this.#getNextChannelMessageTimestamp(
|
|
2546
|
+
channel.channelKey
|
|
2547
|
+
),
|
|
2521
2548
|
}
|
|
2522
2549
|
if (attachment) {
|
|
2523
2550
|
message.attachment = attachment
|
|
@@ -2597,21 +2624,15 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2597
2624
|
let channel = this.#channels.find(c => c.channelKey === value)
|
|
2598
2625
|
if (channel) return channel
|
|
2599
2626
|
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
: matches
|
|
2604
|
-
if (visibleMatches.length === 1) return visibleMatches[0]
|
|
2605
|
-
if (visibleMatches.length > 1) {
|
|
2606
|
-
throw new Error('频道 ID 存在多个候选,请使用 channelKey')
|
|
2627
|
+
channel = this.#channels.find(c => c.channelId === value)
|
|
2628
|
+
if (channel && (!owner || this.#channelHasMember(channel, owner))) {
|
|
2629
|
+
return channel
|
|
2607
2630
|
}
|
|
2608
2631
|
throw new Error('频道不存在')
|
|
2609
2632
|
}
|
|
2610
2633
|
|
|
2611
2634
|
async #createLocalChannel(channelId, type = 'personal', options = {}) {
|
|
2612
|
-
const
|
|
2613
|
-
String(options.fingerprint || '').trim() || createChannelFingerprint()
|
|
2614
|
-
const channelKey = buildChannelKey(channelId, fingerprint)
|
|
2635
|
+
const channelKey = buildChannelKey(channelId)
|
|
2615
2636
|
const writerId = String(options.writerId || '').trim() || createChannelWriterId()
|
|
2616
2637
|
const ns = this.#store.namespace(`channel-${channelKey}`)
|
|
2617
2638
|
const localCore = ns.get({
|
|
@@ -2626,7 +2647,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2626
2647
|
])
|
|
2627
2648
|
const channelInfo = {
|
|
2628
2649
|
channelId,
|
|
2629
|
-
fingerprint,
|
|
2630
2650
|
channelKey,
|
|
2631
2651
|
name: channelId,
|
|
2632
2652
|
type: String(type || 'personal').trim() || 'personal',
|
|
@@ -2651,6 +2671,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2651
2671
|
await this.#joinChannelDiscoveryTopics(channelInfo)
|
|
2652
2672
|
this.#cacheChannelCandidate(this.#channelToCandidate(channelInfo, true))
|
|
2653
2673
|
this.#saveChannelsMetadata()
|
|
2674
|
+
this.#broadcastChannelHello()
|
|
2654
2675
|
return channelInfo
|
|
2655
2676
|
}
|
|
2656
2677
|
|
|
@@ -2658,24 +2679,18 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2658
2679
|
const channelId = normalizeChannelId(
|
|
2659
2680
|
candidateInput.channelId || options.channelId
|
|
2660
2681
|
)
|
|
2661
|
-
const channelKey =
|
|
2662
|
-
|
|
2663
|
-
String(candidateInput.fingerprint || '').trim() ||
|
|
2664
|
-
getChannelFingerprintFromKey(channelId, channelKey)
|
|
2665
|
-
if (!channelId || !fingerprint) {
|
|
2682
|
+
const channelKey = buildChannelKey(channelId)
|
|
2683
|
+
if (!channelId || !channelKey) {
|
|
2666
2684
|
throw new Error('频道候选缺少身份信息')
|
|
2667
2685
|
}
|
|
2668
|
-
const expectedChannelKey = buildChannelKey(channelId, fingerprint)
|
|
2669
|
-
if (channelKey && channelKey !== expectedChannelKey) {
|
|
2670
|
-
throw new Error('频道候选身份格式不匹配')
|
|
2671
|
-
}
|
|
2672
2686
|
|
|
2673
2687
|
const existing = this.#channels.find(
|
|
2674
|
-
channel => channel.channelKey ===
|
|
2688
|
+
channel => channel.channelKey === channelKey
|
|
2675
2689
|
)
|
|
2676
2690
|
if (existing) {
|
|
2677
2691
|
if (this.#upsertChannelMember(existing, options)) {
|
|
2678
2692
|
this.#saveChannelsMetadata()
|
|
2693
|
+
this.#broadcastChannelHello()
|
|
2679
2694
|
}
|
|
2680
2695
|
return this.#formatChannelForResponse(existing, options.ownerAddress)
|
|
2681
2696
|
}
|
|
@@ -2691,7 +2706,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2691
2706
|
const channelInfo = await this.#createLocalChannel(channelId, candidateInput.type || type, {
|
|
2692
2707
|
...options,
|
|
2693
2708
|
ownerAddress,
|
|
2694
|
-
fingerprint,
|
|
2695
2709
|
createdAt: candidateInput.createdAt,
|
|
2696
2710
|
lastMessageAt: candidateInput.lastMessageAt,
|
|
2697
2711
|
writerCoreKeys: candidateInput.writerCoreKeys,
|
|
@@ -2805,7 +2819,18 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2805
2819
|
}
|
|
2806
2820
|
|
|
2807
2821
|
async #discoverChannelCandidates(channelId, options = {}) {
|
|
2808
|
-
|
|
2822
|
+
const getCachedCandidates = () => {
|
|
2823
|
+
const now = Date.now()
|
|
2824
|
+
return [
|
|
2825
|
+
...(this.#channelCandidateCache.get(channelId)?.values() || []),
|
|
2826
|
+
].filter(
|
|
2827
|
+
candidate =>
|
|
2828
|
+
candidate.local ||
|
|
2829
|
+
!candidate.lastSeen ||
|
|
2830
|
+
now - candidate.lastSeen <= CHANNEL_CANDIDATE_TTL
|
|
2831
|
+
)
|
|
2832
|
+
}
|
|
2833
|
+
if (this.#options.disableNetwork) return getCachedCandidates()
|
|
2809
2834
|
const timeout =
|
|
2810
2835
|
Number(options.timeout) >= 0
|
|
2811
2836
|
? Number(options.timeout)
|
|
@@ -2819,15 +2844,7 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2819
2844
|
this.#channelIdDiscoveries.set(channelId, discovery)
|
|
2820
2845
|
}
|
|
2821
2846
|
await sleep(timeout)
|
|
2822
|
-
const
|
|
2823
|
-
const candidates = [
|
|
2824
|
-
...(this.#channelCandidateCache.get(channelId)?.values() || []),
|
|
2825
|
-
].filter(
|
|
2826
|
-
candidate =>
|
|
2827
|
-
candidate.local ||
|
|
2828
|
-
!candidate.lastSeen ||
|
|
2829
|
-
now - candidate.lastSeen <= CHANNEL_CANDIDATE_TTL
|
|
2830
|
-
)
|
|
2847
|
+
const candidates = getCachedCandidates()
|
|
2831
2848
|
if (!hadDiscovery && !this.#channels.some(c => c.channelId === channelId)) {
|
|
2832
2849
|
this.#channelIdDiscoveries.delete(channelId)
|
|
2833
2850
|
this.#chatSwarm
|
|
@@ -2846,7 +2863,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2846
2863
|
byKey.set(candidate.channelKey, {
|
|
2847
2864
|
...candidate,
|
|
2848
2865
|
writerCoreKeys: uniqueStrings(candidate.writerCoreKeys),
|
|
2849
|
-
onlineCount: Number(candidate.onlineCount) || (candidate.local ? 0 : 1),
|
|
2850
2866
|
})
|
|
2851
2867
|
continue
|
|
2852
2868
|
}
|
|
@@ -2858,9 +2874,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2858
2874
|
...existing.writerCoreKeys,
|
|
2859
2875
|
...(candidate.writerCoreKeys || []),
|
|
2860
2876
|
]),
|
|
2861
|
-
onlineCount:
|
|
2862
|
-
Math.max(Number(existing.onlineCount) || 0, 0) +
|
|
2863
|
-
(candidate.local ? 0 : 1),
|
|
2864
2877
|
})
|
|
2865
2878
|
}
|
|
2866
2879
|
return [...byKey.values()]
|
|
@@ -2869,66 +2882,47 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2869
2882
|
#channelToCandidate(channel, local = false) {
|
|
2870
2883
|
return {
|
|
2871
2884
|
channelId: channel.channelId,
|
|
2872
|
-
fingerprint: channel.fingerprint,
|
|
2873
2885
|
channelKey: channel.channelKey,
|
|
2874
2886
|
type: channel.type,
|
|
2875
2887
|
createdAt: channel.createdAt,
|
|
2876
2888
|
lastMessageAt: channel.lastMessageAt || '',
|
|
2877
2889
|
writerCoreKeys: uniqueStrings(channel.writerCoreKeys),
|
|
2878
2890
|
local,
|
|
2879
|
-
onlineCount: local ? 0 : 1,
|
|
2880
2891
|
}
|
|
2881
2892
|
}
|
|
2882
2893
|
|
|
2883
2894
|
#cacheChannelCandidate(candidate) {
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
cache.
|
|
2895
|
+
const channelId = normalizeChannelId(candidate?.channelId)
|
|
2896
|
+
const channelKey = buildChannelKey(channelId)
|
|
2897
|
+
if (!channelId || !channelKey) return
|
|
2898
|
+
if (!this.#channelCandidateCache.has(channelId)) {
|
|
2899
|
+
this.#channelCandidateCache.set(channelId, new Map())
|
|
2900
|
+
}
|
|
2901
|
+
const cache = this.#channelCandidateCache.get(channelId)
|
|
2902
|
+
const existing = cache.get(channelKey)
|
|
2903
|
+
cache.set(channelKey, {
|
|
2891
2904
|
...existing,
|
|
2892
2905
|
...candidate,
|
|
2906
|
+
channelId,
|
|
2907
|
+
channelKey,
|
|
2893
2908
|
writerCoreKeys: uniqueStrings([
|
|
2894
2909
|
...(existing?.writerCoreKeys || []),
|
|
2895
2910
|
...(candidate.writerCoreKeys || []),
|
|
2896
2911
|
]),
|
|
2897
|
-
onlineCount: Math.max(Number(existing?.onlineCount) || 0, 0) + 1,
|
|
2898
2912
|
lastSeen: Date.now(),
|
|
2899
2913
|
})
|
|
2900
2914
|
}
|
|
2901
2915
|
|
|
2902
2916
|
#getCachedChannelCandidate(channelId, channelKey) {
|
|
2903
|
-
const
|
|
2917
|
+
const normalizedChannelId = normalizeChannelId(channelId)
|
|
2918
|
+
const normalizedChannelKey = buildChannelKey(normalizedChannelId)
|
|
2919
|
+
const cache = this.#channelCandidateCache.get(normalizedChannelId)
|
|
2920
|
+
const candidate = cache?.get(channelKey) || cache?.get(normalizedChannelKey)
|
|
2904
2921
|
if (candidate) return candidate
|
|
2905
|
-
const local = this.#channels.find(
|
|
2906
|
-
|
|
2907
|
-
}
|
|
2908
|
-
|
|
2909
|
-
#formatChannelCandidateForResponse(candidate, ownerAddress = '') {
|
|
2910
|
-
const owner = normalizeOwnerAddress(ownerAddress)
|
|
2911
|
-
const localChannel = this.#channels.find(
|
|
2912
|
-
channel => channel.channelKey === candidate.channelKey
|
|
2922
|
+
const local = this.#channels.find(
|
|
2923
|
+
channel => channel.channelKey === normalizedChannelKey
|
|
2913
2924
|
)
|
|
2914
|
-
|
|
2915
|
-
localChannel && owner
|
|
2916
|
-
? localChannel.remarks?.[owner] || ''
|
|
2917
|
-
: candidate.local
|
|
2918
|
-
? ''
|
|
2919
|
-
: `${candidate.channelId}-网络`
|
|
2920
|
-
return {
|
|
2921
|
-
channelId: candidate.channelId,
|
|
2922
|
-
fingerprint: candidate.fingerprint,
|
|
2923
|
-
channelKey: candidate.channelKey,
|
|
2924
|
-
name: candidate.channelId,
|
|
2925
|
-
type: candidate.type || 'public',
|
|
2926
|
-
createdAt: candidate.createdAt || '',
|
|
2927
|
-
lastMessageAt: candidate.lastMessageAt || '',
|
|
2928
|
-
remark,
|
|
2929
|
-
local: Boolean(candidate.local),
|
|
2930
|
-
onlineCount: Number(candidate.onlineCount) || 0,
|
|
2931
|
-
}
|
|
2925
|
+
return local ? this.#channelToCandidate(local, true) : null
|
|
2932
2926
|
}
|
|
2933
2927
|
|
|
2934
2928
|
#formatChannelForResponse(channel, ownerAddress = '') {
|
|
@@ -2936,7 +2930,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2936
2930
|
return {
|
|
2937
2931
|
name: channel.channelId,
|
|
2938
2932
|
channelId: channel.channelId,
|
|
2939
|
-
fingerprint: channel.fingerprint,
|
|
2940
2933
|
channelKey: channel.channelKey,
|
|
2941
2934
|
key: channel.channelKey,
|
|
2942
2935
|
coreKey: channel.localWriterCoreKey,
|
|
@@ -3046,6 +3039,31 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
3046
3039
|
)
|
|
3047
3040
|
}
|
|
3048
3041
|
|
|
3042
|
+
async #getNextChannelMessageTimestamp(channelKey) {
|
|
3043
|
+
const coresMap = this.#channelCores.get(channelKey)
|
|
3044
|
+
let maxTimestamp = 0
|
|
3045
|
+
|
|
3046
|
+
if (coresMap) {
|
|
3047
|
+
for (const [, core] of coresMap) {
|
|
3048
|
+
for (let i = 0; i < core.length; i++) {
|
|
3049
|
+
try {
|
|
3050
|
+
const entry = await core.get(i)
|
|
3051
|
+
if (entry?.type === 'message') {
|
|
3052
|
+
maxTimestamp = Math.max(
|
|
3053
|
+
maxTimestamp,
|
|
3054
|
+
Number(entry.timestamp) || 0
|
|
3055
|
+
)
|
|
3056
|
+
}
|
|
3057
|
+
} catch {
|
|
3058
|
+
break
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
3063
|
+
|
|
3064
|
+
return Math.max(Date.now(), maxTimestamp + 1)
|
|
3065
|
+
}
|
|
3066
|
+
|
|
3049
3067
|
#normalizeChannelMessageForResponse(channelKey, message) {
|
|
3050
3068
|
const channel = this.#channels.find(item => item.channelKey === channelKey)
|
|
3051
3069
|
const authorAddress = normalizeOwnerAddress(message?.author)
|
|
@@ -3523,7 +3541,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
3523
3541
|
}
|
|
3524
3542
|
return {
|
|
3525
3543
|
channelId: channel.channelId,
|
|
3526
|
-
fingerprint: channel.fingerprint,
|
|
3527
3544
|
channelKey: channel.channelKey,
|
|
3528
3545
|
type: channel.type,
|
|
3529
3546
|
createdAt: channel.createdAt,
|
|
@@ -3701,15 +3718,9 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
3701
3718
|
async #applyUserSyncChannelRecord(ownerAddress, record, timestamp) {
|
|
3702
3719
|
if (!record || typeof record !== 'object') return false
|
|
3703
3720
|
const channelId = normalizeChannelId(record.channelId)
|
|
3704
|
-
const
|
|
3705
|
-
const
|
|
3706
|
-
|
|
3707
|
-
const recordChannelKey = normalizeChannelKey(record.channelKey)
|
|
3708
|
-
if (recordChannelKey && recordChannelKey !== expectedChannelKey) {
|
|
3709
|
-
return false
|
|
3710
|
-
}
|
|
3711
|
-
const channelKey = recordChannelKey || expectedChannelKey
|
|
3712
|
-
if (!channelId || !fingerprint || !channelKey) return false
|
|
3721
|
+
const expectedChannelKey = buildChannelKey(channelId)
|
|
3722
|
+
const channelKey = expectedChannelKey
|
|
3723
|
+
if (!channelId || !channelKey) return false
|
|
3713
3724
|
const syncUpdatedAt = getSyncTimestamp(record.syncUpdatedAt, timestamp)
|
|
3714
3725
|
const entityKey = `channel:${channelKey}`
|
|
3715
3726
|
if (!this.#shouldApplyUserSyncEntity(ownerAddress, entityKey, syncUpdatedAt)) {
|
|
@@ -3721,7 +3732,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
3721
3732
|
if (!channel) {
|
|
3722
3733
|
channel = {
|
|
3723
3734
|
channelId,
|
|
3724
|
-
fingerprint,
|
|
3725
3735
|
channelKey,
|
|
3726
3736
|
name: channelId,
|
|
3727
3737
|
createdAt:
|
|
@@ -4668,16 +4678,11 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
4668
4678
|
.filter(channel => channel && typeof channel === 'object')
|
|
4669
4679
|
.map(channel => {
|
|
4670
4680
|
const channelId = normalizeChannelId(channel.channelId)
|
|
4671
|
-
const fingerprint = String(channel.fingerprint || '').trim()
|
|
4672
|
-
const expectedChannelKey =
|
|
4673
|
-
channelId && fingerprint
|
|
4674
|
-
? buildChannelKey(channelId, fingerprint)
|
|
4675
|
-
: ''
|
|
4676
4681
|
const channelKey = normalizeChannelKey(channel.channelKey)
|
|
4682
|
+
const expectedChannelKey = buildChannelKey(channelId)
|
|
4677
4683
|
return {
|
|
4678
4684
|
...channel,
|
|
4679
4685
|
channelId,
|
|
4680
|
-
fingerprint,
|
|
4681
4686
|
channelKey,
|
|
4682
4687
|
expectedChannelKey,
|
|
4683
4688
|
name: channelId,
|
|
@@ -4687,7 +4692,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
4687
4692
|
.filter(
|
|
4688
4693
|
channel =>
|
|
4689
4694
|
CHANNEL_NAME_REGEX.test(channel.channelId) &&
|
|
4690
|
-
channel.fingerprint &&
|
|
4691
4695
|
channel.channelKey === channel.expectedChannelKey &&
|
|
4692
4696
|
channel.writerId &&
|
|
4693
4697
|
channel.localWriterCoreKey
|
|
@@ -4710,7 +4714,6 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
4710
4714
|
.filter(channel => !TRANSIENT_CHANNEL_TYPES.has(channel?.type))
|
|
4711
4715
|
.map(channel => ({
|
|
4712
4716
|
channelId: channel.channelId,
|
|
4713
|
-
fingerprint: channel.fingerprint,
|
|
4714
4717
|
channelKey: channel.channelKey,
|
|
4715
4718
|
name: channel.channelId,
|
|
4716
4719
|
type: channel.type,
|
|
@@ -4840,13 +4843,9 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
4840
4843
|
}
|
|
4841
4844
|
}
|
|
4842
4845
|
|
|
4843
|
-
|
|
4844
|
-
const stream = conn
|
|
4845
|
-
let connectedPeerId = null
|
|
4846
|
-
|
|
4846
|
+
#buildChannelHelloMessage() {
|
|
4847
4847
|
const channels = this.#channels.map(channel => ({
|
|
4848
4848
|
channelId: channel.channelId,
|
|
4849
|
-
fingerprint: channel.fingerprint,
|
|
4850
4849
|
channelKey: channel.channelKey,
|
|
4851
4850
|
type: channel.type,
|
|
4852
4851
|
createdAt: channel.createdAt,
|
|
@@ -4865,122 +4864,157 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
4865
4864
|
})
|
|
4866
4865
|
)
|
|
4867
4866
|
|
|
4868
|
-
|
|
4867
|
+
return {
|
|
4869
4868
|
type: 'channel-hello',
|
|
4870
4869
|
peerId: this.getNodeId(),
|
|
4871
4870
|
authorName: this.getNodeId().slice(0, 4),
|
|
4872
4871
|
channels,
|
|
4873
4872
|
userSyncSessions,
|
|
4874
|
-
}
|
|
4873
|
+
}
|
|
4874
|
+
}
|
|
4875
4875
|
|
|
4876
|
+
#sendChannelHello(stream) {
|
|
4877
|
+
if (!stream || stream.destroyed || stream.writableEnded) {
|
|
4878
|
+
this.#channelStreams.delete(stream)
|
|
4879
|
+
return false
|
|
4880
|
+
}
|
|
4876
4881
|
try {
|
|
4877
|
-
stream.write(
|
|
4882
|
+
stream.write(`${JSON.stringify(this.#buildChannelHelloMessage())}\n`)
|
|
4883
|
+
return true
|
|
4878
4884
|
} catch {
|
|
4879
|
-
|
|
4885
|
+
this.#channelStreams.delete(stream)
|
|
4886
|
+
return false
|
|
4880
4887
|
}
|
|
4888
|
+
}
|
|
4881
4889
|
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
const remoteChannels = Array.isArray(msg.channels)
|
|
4889
|
-
? msg.channels
|
|
4890
|
-
.filter(channel => channel && typeof channel === 'object')
|
|
4891
|
-
.map(channel => ({
|
|
4892
|
-
channelId: normalizeChannelId(channel.channelId),
|
|
4893
|
-
fingerprint: String(channel.fingerprint || '').trim(),
|
|
4894
|
-
channelKey: normalizeChannelKey(channel.channelKey),
|
|
4895
|
-
type: String(channel.type || 'public').trim() || 'public',
|
|
4896
|
-
createdAt:
|
|
4897
|
-
typeof channel.createdAt === 'string'
|
|
4898
|
-
? channel.createdAt
|
|
4899
|
-
: '',
|
|
4900
|
-
lastMessageAt:
|
|
4901
|
-
typeof channel.lastMessageAt === 'string'
|
|
4902
|
-
? channel.lastMessageAt
|
|
4903
|
-
: '',
|
|
4904
|
-
writerCoreKeys: uniqueStrings(channel.writerCoreKeys),
|
|
4905
|
-
}))
|
|
4906
|
-
.filter(
|
|
4907
|
-
channel =>
|
|
4908
|
-
channel.channelId &&
|
|
4909
|
-
channel.fingerprint &&
|
|
4910
|
-
channel.channelKey
|
|
4911
|
-
)
|
|
4912
|
-
: []
|
|
4913
|
-
|
|
4914
|
-
for (const remoteChannel of remoteChannels) {
|
|
4915
|
-
this.#cacheChannelCandidate({
|
|
4916
|
-
...remoteChannel,
|
|
4917
|
-
local: false,
|
|
4918
|
-
peerId: msg.peerId,
|
|
4919
|
-
onlineCount: 1,
|
|
4920
|
-
})
|
|
4890
|
+
#broadcastChannelHello() {
|
|
4891
|
+
for (const stream of [...this.#channelStreams]) {
|
|
4892
|
+
this.#sendChannelHello(stream)
|
|
4893
|
+
}
|
|
4894
|
+
}
|
|
4921
4895
|
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
)
|
|
4925
|
-
if (!localChannel) continue
|
|
4926
|
-
|
|
4927
|
-
const peers = this.#channelPeers.get(localChannel.channelKey)
|
|
4928
|
-
if (peers) {
|
|
4929
|
-
peers.set(msg.peerId, {
|
|
4930
|
-
peerId: msg.peerId,
|
|
4931
|
-
authorName: msg.authorName,
|
|
4932
|
-
lastSeen: Date.now(),
|
|
4933
|
-
})
|
|
4934
|
-
}
|
|
4896
|
+
async #processChannelHelloMessage(msg) {
|
|
4897
|
+
if (msg.type !== 'channel-hello') return null
|
|
4935
4898
|
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4899
|
+
const remoteChannels = Array.isArray(msg.channels)
|
|
4900
|
+
? msg.channels
|
|
4901
|
+
.filter(channel => channel && typeof channel === 'object')
|
|
4902
|
+
.map(channel => {
|
|
4903
|
+
const channelId = normalizeChannelId(channel.channelId)
|
|
4904
|
+
return {
|
|
4905
|
+
channelId,
|
|
4906
|
+
channelKey: buildChannelKey(channelId),
|
|
4907
|
+
type: String(channel.type || 'public').trim() || 'public',
|
|
4908
|
+
createdAt:
|
|
4909
|
+
typeof channel.createdAt === 'string' ? channel.createdAt : '',
|
|
4910
|
+
lastMessageAt:
|
|
4911
|
+
typeof channel.lastMessageAt === 'string'
|
|
4912
|
+
? channel.lastMessageAt
|
|
4913
|
+
: '',
|
|
4914
|
+
writerCoreKeys: uniqueStrings(channel.writerCoreKeys),
|
|
4946
4915
|
}
|
|
4947
|
-
}
|
|
4916
|
+
})
|
|
4917
|
+
.filter(channel => channel.channelId && channel.channelKey)
|
|
4918
|
+
: []
|
|
4948
4919
|
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
writerCoreKeys: uniqueStrings(session.writerCoreKeys),
|
|
4956
|
-
}))
|
|
4957
|
-
.filter(session => session.ownerAddress && session.syncId)
|
|
4958
|
-
: []
|
|
4959
|
-
|
|
4960
|
-
for (const remoteSession of remoteUserSyncSessions) {
|
|
4961
|
-
const localSession = this.#userSyncSessions.get(
|
|
4962
|
-
remoteSession.ownerAddress
|
|
4963
|
-
)
|
|
4964
|
-
if (!localSession || localSession.syncId !== remoteSession.syncId) {
|
|
4965
|
-
continue
|
|
4966
|
-
}
|
|
4967
|
-
await this.#mergeUserSyncWriterCoreKeys(
|
|
4968
|
-
localSession,
|
|
4969
|
-
remoteSession.writerCoreKeys
|
|
4970
|
-
)
|
|
4971
|
-
}
|
|
4920
|
+
for (const remoteChannel of remoteChannels) {
|
|
4921
|
+
this.#cacheChannelCandidate({
|
|
4922
|
+
...remoteChannel,
|
|
4923
|
+
local: false,
|
|
4924
|
+
peerId: msg.peerId,
|
|
4925
|
+
})
|
|
4972
4926
|
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4927
|
+
const localChannel = this.#channels.find(
|
|
4928
|
+
channel => channel.channelKey === remoteChannel.channelKey
|
|
4929
|
+
)
|
|
4930
|
+
if (!localChannel) continue
|
|
4931
|
+
|
|
4932
|
+
const peers = this.#channelPeers.get(localChannel.channelKey)
|
|
4933
|
+
if (peers) {
|
|
4934
|
+
peers.set(msg.peerId, {
|
|
4935
|
+
peerId: msg.peerId,
|
|
4936
|
+
authorName: msg.authorName,
|
|
4937
|
+
lastSeen: Date.now(),
|
|
4938
|
+
})
|
|
4939
|
+
}
|
|
4940
|
+
|
|
4941
|
+
for (const writerCoreKey of remoteChannel.writerCoreKeys) {
|
|
4942
|
+
if (
|
|
4943
|
+
writerCoreKey &&
|
|
4944
|
+
writerCoreKey !== this.#channelLocalCoreKey.get(localChannel.channelKey)
|
|
4945
|
+
) {
|
|
4946
|
+
await this.#openRemoteChannelCore(
|
|
4947
|
+
localChannel.channelKey,
|
|
4948
|
+
writerCoreKey
|
|
4949
|
+
)
|
|
4950
|
+
}
|
|
4951
|
+
}
|
|
4952
|
+
}
|
|
4953
|
+
|
|
4954
|
+
const remoteUserSyncSessions = Array.isArray(msg.userSyncSessions)
|
|
4955
|
+
? msg.userSyncSessions
|
|
4956
|
+
.filter(session => session && typeof session === 'object')
|
|
4957
|
+
.map(session => ({
|
|
4958
|
+
ownerAddress: normalizeOwnerAddress(session.ownerAddress),
|
|
4959
|
+
syncId: String(session.syncId || '').trim(),
|
|
4960
|
+
writerCoreKeys: uniqueStrings(session.writerCoreKeys),
|
|
4961
|
+
}))
|
|
4962
|
+
.filter(session => session.ownerAddress && session.syncId)
|
|
4963
|
+
: []
|
|
4964
|
+
|
|
4965
|
+
for (const remoteSession of remoteUserSyncSessions) {
|
|
4966
|
+
const localSession = this.#userSyncSessions.get(
|
|
4967
|
+
remoteSession.ownerAddress
|
|
4968
|
+
)
|
|
4969
|
+
if (!localSession || localSession.syncId !== remoteSession.syncId) {
|
|
4970
|
+
continue
|
|
4971
|
+
}
|
|
4972
|
+
await this.#mergeUserSyncWriterCoreKeys(
|
|
4973
|
+
localSession,
|
|
4974
|
+
remoteSession.writerCoreKeys
|
|
4975
|
+
)
|
|
4976
|
+
}
|
|
4977
|
+
|
|
4978
|
+
this.emit('channel:peer:online', {
|
|
4979
|
+
peerId: msg.peerId,
|
|
4980
|
+
authorName: msg.authorName,
|
|
4981
|
+
})
|
|
4982
|
+
|
|
4983
|
+
return msg.peerId
|
|
4984
|
+
}
|
|
4985
|
+
|
|
4986
|
+
async #handleChannelConnection(conn) {
|
|
4987
|
+
const stream = conn
|
|
4988
|
+
let connectedPeerId = null
|
|
4989
|
+
let readBuffer = ''
|
|
4990
|
+
let closed = false
|
|
4991
|
+
|
|
4992
|
+
this.#channelStreams.add(stream)
|
|
4993
|
+
if (!this.#sendChannelHello(stream)) return
|
|
4994
|
+
|
|
4995
|
+
stream.on('data', async data => {
|
|
4996
|
+
readBuffer += data.toString()
|
|
4997
|
+
let newlineIndex = readBuffer.indexOf('\n')
|
|
4998
|
+
while (newlineIndex !== -1) {
|
|
4999
|
+
const line = readBuffer.slice(0, newlineIndex).trim()
|
|
5000
|
+
readBuffer = readBuffer.slice(newlineIndex + 1)
|
|
5001
|
+
newlineIndex = readBuffer.indexOf('\n')
|
|
5002
|
+
if (!line) continue
|
|
5003
|
+
try {
|
|
5004
|
+
const peerId = await this.#processChannelHelloMessage(
|
|
5005
|
+
JSON.parse(line)
|
|
5006
|
+
)
|
|
5007
|
+
if (peerId) connectedPeerId = peerId
|
|
5008
|
+
} catch (err) {
|
|
5009
|
+
console.warn(`[MostBox] Failed to process channel data:`, err.message)
|
|
4977
5010
|
}
|
|
4978
|
-
} catch (err) {
|
|
4979
|
-
console.warn(`[MostBox] Failed to process channel data:`, err.message)
|
|
4980
5011
|
}
|
|
4981
5012
|
})
|
|
4982
5013
|
|
|
4983
|
-
|
|
5014
|
+
const cleanup = () => {
|
|
5015
|
+
if (closed) return
|
|
5016
|
+
closed = true
|
|
5017
|
+
this.#channelStreams.delete(stream)
|
|
4984
5018
|
if (connectedPeerId) {
|
|
4985
5019
|
for (const [, peers] of this.#channelPeers) {
|
|
4986
5020
|
if (peers.has(connectedPeerId)) {
|
|
@@ -4993,7 +5027,10 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
4993
5027
|
}
|
|
4994
5028
|
}
|
|
4995
5029
|
}
|
|
4996
|
-
}
|
|
5030
|
+
}
|
|
5031
|
+
|
|
5032
|
+
stream.on('close', cleanup)
|
|
5033
|
+
stream.on('error', cleanup)
|
|
4997
5034
|
}
|
|
4998
5035
|
|
|
4999
5036
|
/**
|