most-box 0.3.5 → 0.3.6
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/out/about/index.html +2 -2
- package/out/admin/index.html +0 -0
- package/out/app/index.html +0 -0
- package/out/assets/{AccountMenu-D-wa5tux.js → AccountMenu-B26qus-Q.js} +1 -1
- package/out/assets/{AppShell-BdRKZIZ9.js → AppShell-BiYQsNO8.js} +1 -1
- package/out/assets/{AppTop-BE5kKBYH.js → AppTop-D-qYC0ep.js} +1 -1
- package/out/assets/ChatRestoringIndicator-CLV78gNW.js +1 -0
- package/out/assets/{CopyButton-DnFqfCN9.js → CopyButton-BrEkzqEq.js} +1 -1
- package/out/assets/{MarketingHeader-8gNzHawY.js → MarketingHeader-CJXpogPY.js} +1 -1
- package/out/assets/{MarketingLayout-BPdjpEfc.js → MarketingLayout-Ch4sw5Qm.js} +1 -1
- package/out/assets/{MilkdownEditor-C388llV6.js → MilkdownEditor-DfhuT9eU.js} +1 -1
- package/out/assets/{OpenSidebarButton-OP8zl7ad.js → OpenSidebarButton-Bvwr7B6k.js} +1 -1
- package/out/assets/{chatJoinInvite-CeC6H6t8.js → chatJoinInvite-7tuD-STm.js} +1 -1
- package/out/assets/i18n-DKsg67mH.js +1 -0
- package/out/assets/index-FEwag7ej.css +1 -0
- package/out/assets/{index-D1mozno-.js → index-mjFWLwue.js} +3 -3
- package/out/assets/{index.lazy-B1iJsXQ8.js → index.lazy-8GSHLIGt.js} +1 -1
- package/out/assets/{index.lazy-CX2l1_Gi.js → index.lazy-B3jH9yBA.js} +1 -1
- package/out/assets/index.lazy-B5JMiv3z.js +1 -0
- package/out/assets/{index.lazy-CVFcovqX.js → index.lazy-BAr7-dhf.js} +1 -1
- package/out/assets/index.lazy-BMFwyE_Z.js +3 -0
- package/out/assets/{index.lazy-Bz77Jlzu.js → index.lazy-BbA_1LnY.js} +1 -1
- package/out/assets/{index.lazy-m9XAzMt1.js → index.lazy-BmpOpQqg.js} +1 -1
- package/out/assets/{index.lazy-FvXAYdEO.js → index.lazy-CB3C6kMj.js} +1 -1
- package/out/assets/{index.lazy-ClrSkRx5.js → index.lazy-CVOAOO0_.js} +1 -1
- package/out/assets/{index.lazy-CflWbocP.js → index.lazy-CyFSwJuo.js} +1 -1
- package/out/assets/{index.lazy-DzaN9qwA.js → index.lazy-D3iEq2ZW.js} +2 -2
- package/out/assets/{index.lazy-CsTaY5WS.js → index.lazy-F3iHN_Ao.js} +1 -1
- package/out/assets/{index.lazy-D7E1pb1S.js → index.lazy-PnZR3z1V.js} +1 -1
- package/out/assets/{index.lazy-59kmyNMW.js → index.lazy-qDpRLqXu.js} +1 -1
- package/out/assets/{index.lazy-C_J9afOY.js → index.lazy-s6Ok77GI.js} +1 -1
- package/out/assets/{saveLocalFile-2CNt4X0T.js → saveLocalFile-BLTn3Z18.js} +1 -1
- package/out/assets/useChannelMessages-DKlO_2oW.js +4 -0
- package/out/assets/{useGameRoom-asr2gyY6.js → useGameRoom-DLN636mz.js} +1 -1
- package/out/chat/index.html +0 -0
- package/out/chat/join/demo/index.html +0 -0
- package/out/chat/join/index.html +0 -0
- package/out/download/index.html +2 -2
- 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 +2 -2
- package/out/note/index.html +0 -0
- package/out/ping/index.html +2 -2
- package/out/profile/index.html +0 -0
- package/out/web3/index.html +0 -0
- package/package.json +1 -1
- package/server/src/index.js +191 -76
- package/out/assets/i18n-C6yaqRst.js +0 -1
- package/out/assets/index-C6IjeTBh.css +0 -1
- package/out/assets/index.lazy-CazlK1-Y.js +0 -3
- package/out/assets/index.lazy-JwrS2vDI.js +0 -1
- package/out/assets/useChannelMessages-DDBqKBXU.js +0 -4
package/server/src/index.js
CHANGED
|
@@ -293,6 +293,42 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
293
293
|
return this.#pruneStaleChannelPresence()
|
|
294
294
|
}
|
|
295
295
|
|
|
296
|
+
#isClosedSessionError(err) {
|
|
297
|
+
if (!err || typeof err !== 'object') return false
|
|
298
|
+
const code = String(err.code || '')
|
|
299
|
+
const message = String(err.message || '')
|
|
300
|
+
return (
|
|
301
|
+
code === 'SESSION_CLOSED' ||
|
|
302
|
+
message.includes('SESSION_CLOSED') ||
|
|
303
|
+
message.includes('closed session')
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async #reopenChannelLocalWriter(channel) {
|
|
308
|
+
const channelKey = channel.channelKey
|
|
309
|
+
const localKeyHex =
|
|
310
|
+
this.#channelLocalCoreKey.get(channelKey) || channel.localWriterCoreKey
|
|
311
|
+
const coresMap = this.#channelCores.get(channelKey)
|
|
312
|
+
const staleCore = localKeyHex && coresMap ? coresMap.get(localKeyHex) : null
|
|
313
|
+
|
|
314
|
+
if (staleCore) {
|
|
315
|
+
coresMap.delete(localKeyHex)
|
|
316
|
+
await staleCore.close().catch(() => {})
|
|
317
|
+
}
|
|
318
|
+
this.#channelLocalCoreKey.delete(channelKey)
|
|
319
|
+
|
|
320
|
+
await this.#openChannelRuntime(channel)
|
|
321
|
+
|
|
322
|
+
const reopenedKeyHex = this.#channelLocalCoreKey.get(channelKey)
|
|
323
|
+
const reopenedCore = reopenedKeyHex
|
|
324
|
+
? this.#channelCores.get(channelKey)?.get(reopenedKeyHex)
|
|
325
|
+
: null
|
|
326
|
+
if (!reopenedCore) {
|
|
327
|
+
throw new Error('频道未初始化或无可写 core')
|
|
328
|
+
}
|
|
329
|
+
return reopenedCore
|
|
330
|
+
}
|
|
331
|
+
|
|
296
332
|
/**
|
|
297
333
|
* 初始化引擎 — 必须在调用其他方法之前调用
|
|
298
334
|
*/
|
|
@@ -624,29 +660,38 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
624
660
|
const publishedBucket = this.#getPublishedBucket(ownerAddress, true)
|
|
625
661
|
// 检查相同内容是否已存在
|
|
626
662
|
const existingIndex = publishedBucket.findIndex(f => f.cid === cidString)
|
|
663
|
+
const repairingMissingContent = existingIndex !== -1
|
|
627
664
|
if (existingIndex !== -1) {
|
|
628
665
|
const existing = publishedBucket[existingIndex]
|
|
629
|
-
await this.#
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
this.#upsertHolding({
|
|
634
|
-
cid: cidString,
|
|
635
|
-
fileName: existing.fileName,
|
|
636
|
-
size: fileSize,
|
|
637
|
-
driveName: name,
|
|
638
|
-
source: 'published',
|
|
666
|
+
const existingContent = await this.#getLocalCidContent(cidString, {
|
|
667
|
+
ownerAddress,
|
|
668
|
+
public: true,
|
|
669
|
+
allowHoldingFallback: true,
|
|
639
670
|
})
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
671
|
+
if (existingContent) {
|
|
672
|
+
await this.#joinCidTopicInternal(cidString, {
|
|
673
|
+
server: true,
|
|
674
|
+
client: false,
|
|
675
|
+
})
|
|
676
|
+
this.#upsertHolding({
|
|
677
|
+
cid: cidString,
|
|
678
|
+
fileName: existing.fileName,
|
|
679
|
+
size: fileSize,
|
|
680
|
+
driveName: name,
|
|
681
|
+
source: 'published',
|
|
682
|
+
})
|
|
683
|
+
return {
|
|
684
|
+
cid: cidString,
|
|
685
|
+
link: buildMostLink(cidString, existing.fileName),
|
|
686
|
+
fileName: existing.fileName,
|
|
687
|
+
alreadyExists: true,
|
|
688
|
+
}
|
|
645
689
|
}
|
|
646
690
|
}
|
|
647
691
|
|
|
648
692
|
this.#assertDisplayNameAvailable(safeFileName, {
|
|
649
693
|
ownerAddress,
|
|
694
|
+
excludeCid: repairingMissingContent ? cidString : undefined,
|
|
650
695
|
})
|
|
651
696
|
|
|
652
697
|
// 获取或创建该 CID 对应的 drive
|
|
@@ -667,40 +712,17 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
667
712
|
|
|
668
713
|
// Hyperdrive 中用 CID 作为 key 存储(解耦目录结构)
|
|
669
714
|
const driveKey = '/' + cidString
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
let offset = 0
|
|
675
|
-
const waitForDrain = () =>
|
|
676
|
-
new Promise(resolve => ws.once('drain', resolve))
|
|
677
|
-
|
|
678
|
-
try {
|
|
679
|
-
while (offset < content.length) {
|
|
680
|
-
const chunk = content.slice(offset, offset + FILE_WRITE_CHUNK_SIZE)
|
|
681
|
-
const canContinue = ws.write(chunk)
|
|
682
|
-
offset += chunk.length
|
|
683
|
-
if (!canContinue && offset < content.length) {
|
|
684
|
-
await waitForDrain()
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
ws.end()
|
|
688
|
-
await new Promise((resolve, reject) => {
|
|
689
|
-
ws.on('finish', resolve)
|
|
690
|
-
ws.on('error', reject)
|
|
691
|
-
})
|
|
692
|
-
} catch (err) {
|
|
693
|
-
ws.destroy()
|
|
715
|
+
try {
|
|
716
|
+
await this.#writeDriveFile(drive, driveKey, content, cleanPath)
|
|
717
|
+
} catch (err) {
|
|
718
|
+
if (!this.#isClosedSessionError(err)) {
|
|
694
719
|
throw err
|
|
695
720
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
rs.pipe(ws)
|
|
700
|
-
ws.on('finish', resolve)
|
|
701
|
-
ws.on('error', reject)
|
|
702
|
-
rs.on('error', reject)
|
|
721
|
+
drive = await this.#reopenDrive(name, {
|
|
722
|
+
server: true,
|
|
723
|
+
client: false,
|
|
703
724
|
})
|
|
725
|
+
await this.#writeDriveFile(drive, driveKey, content, cleanPath)
|
|
704
726
|
}
|
|
705
727
|
|
|
706
728
|
// 存储 displayName(用户看到的文件夹路径),不存储 drivePath
|
|
@@ -715,7 +737,11 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
715
737
|
starred: false,
|
|
716
738
|
syncUpdatedAt: now,
|
|
717
739
|
}
|
|
718
|
-
|
|
740
|
+
if (repairingMissingContent) {
|
|
741
|
+
publishedBucket[existingIndex] = fileRecord
|
|
742
|
+
} else {
|
|
743
|
+
publishedBucket.push(fileRecord)
|
|
744
|
+
}
|
|
719
745
|
this.#savePublishedMetadata()
|
|
720
746
|
this.#upsertHolding({
|
|
721
747
|
cid: cidString,
|
|
@@ -2214,7 +2240,10 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2214
2240
|
async #hasLocalDriveContent(drive, key) {
|
|
2215
2241
|
try {
|
|
2216
2242
|
return await drive.has(key)
|
|
2217
|
-
} catch {
|
|
2243
|
+
} catch (err) {
|
|
2244
|
+
if (this.#isClosedSessionError(err)) {
|
|
2245
|
+
throw err
|
|
2246
|
+
}
|
|
2218
2247
|
return false
|
|
2219
2248
|
}
|
|
2220
2249
|
}
|
|
@@ -2234,42 +2263,53 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2234
2263
|
}
|
|
2235
2264
|
const holding = this.#holdings.find(item => item.cid === cid)
|
|
2236
2265
|
const { driveName } = this.#getCidInfo(cid)
|
|
2237
|
-
|
|
2266
|
+
let drive = await this.#getOrCreateDrive(
|
|
2238
2267
|
fileRecord?.driveName || holding?.driveName || driveName,
|
|
2239
2268
|
{ server: true, client: false }
|
|
2240
2269
|
)
|
|
2241
2270
|
const driveKey = '/' + cid
|
|
2242
2271
|
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2272
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
2273
|
+
try {
|
|
2274
|
+
const entry = await drive.entry(driveKey, { wait: false })
|
|
2275
|
+
if (!entry?.value?.blob) {
|
|
2276
|
+
return null
|
|
2277
|
+
}
|
|
2278
|
+
const hasContent = await this.#hasLocalDriveContent(drive, driveKey)
|
|
2279
|
+
if (!hasContent) {
|
|
2280
|
+
return null
|
|
2281
|
+
}
|
|
2252
2282
|
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
size,
|
|
2262
|
-
fileRecord: fileRecord || {
|
|
2263
|
-
cid,
|
|
2264
|
-
fileName: holding?.fileName || cid,
|
|
2265
|
-
driveName: holding?.driveName || driveName,
|
|
2283
|
+
const size =
|
|
2284
|
+
Number(entry.value.blob.byteLength) ||
|
|
2285
|
+
Number(fileRecord?.size) ||
|
|
2286
|
+
Number(holding?.size) ||
|
|
2287
|
+
0
|
|
2288
|
+
return {
|
|
2289
|
+
drive,
|
|
2290
|
+
entry,
|
|
2266
2291
|
size,
|
|
2267
|
-
|
|
2268
|
-
|
|
2292
|
+
fileRecord: fileRecord || {
|
|
2293
|
+
cid,
|
|
2294
|
+
fileName: holding?.fileName || cid,
|
|
2295
|
+
driveName: holding?.driveName || driveName,
|
|
2296
|
+
size,
|
|
2297
|
+
ownerAddress,
|
|
2298
|
+
},
|
|
2299
|
+
}
|
|
2300
|
+
} catch (err) {
|
|
2301
|
+
if (attempt === 0 && this.#isClosedSessionError(err)) {
|
|
2302
|
+
drive = await this.#reopenDrive(
|
|
2303
|
+
fileRecord?.driveName || holding?.driveName || driveName,
|
|
2304
|
+
{ server: true, client: false }
|
|
2305
|
+
)
|
|
2306
|
+
continue
|
|
2307
|
+
}
|
|
2308
|
+
return null
|
|
2269
2309
|
}
|
|
2270
|
-
} catch {
|
|
2271
|
-
return null
|
|
2272
2310
|
}
|
|
2311
|
+
|
|
2312
|
+
return null
|
|
2273
2313
|
}
|
|
2274
2314
|
|
|
2275
2315
|
// --- 频道管理 ---
|
|
@@ -2721,7 +2761,15 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
2721
2761
|
if (event) message.event = event
|
|
2722
2762
|
}
|
|
2723
2763
|
|
|
2724
|
-
|
|
2764
|
+
try {
|
|
2765
|
+
await core.append(message)
|
|
2766
|
+
} catch (err) {
|
|
2767
|
+
if (!this.#isClosedSessionError(err)) {
|
|
2768
|
+
throw err
|
|
2769
|
+
}
|
|
2770
|
+
const reopenedCore = await this.#reopenChannelLocalWriter(channel)
|
|
2771
|
+
await reopenedCore.append(message)
|
|
2772
|
+
}
|
|
2725
2773
|
if (channel) {
|
|
2726
2774
|
channel.lastMessageAt = new Date(message.timestamp).toISOString()
|
|
2727
2775
|
this.#saveChannelsMetadata()
|
|
@@ -4085,6 +4133,19 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
4085
4133
|
if (!this.#holdings.some(current => current.cid === holding.cid)) {
|
|
4086
4134
|
return
|
|
4087
4135
|
}
|
|
4136
|
+
const localContent = await this.#getLocalCidContent(holding.cid, {
|
|
4137
|
+
public: true,
|
|
4138
|
+
allowHoldingFallback: true,
|
|
4139
|
+
})
|
|
4140
|
+
if (!localContent) {
|
|
4141
|
+
this.#setSeedState(holding.cid, {
|
|
4142
|
+
status: 'error',
|
|
4143
|
+
topic: holding.topic,
|
|
4144
|
+
driveName: holding.driveName,
|
|
4145
|
+
error: 'Local CID content missing',
|
|
4146
|
+
})
|
|
4147
|
+
return
|
|
4148
|
+
}
|
|
4088
4149
|
await this.#joinCidTopicInternal(holding.cid, {
|
|
4089
4150
|
server: true,
|
|
4090
4151
|
client: false,
|
|
@@ -4312,6 +4373,60 @@ export class MostBoxEngine extends EventEmitter {
|
|
|
4312
4373
|
return drive
|
|
4313
4374
|
}
|
|
4314
4375
|
|
|
4376
|
+
async #reopenDrive(name, options = { server: true, client: false }) {
|
|
4377
|
+
const staleDrive = this.#drives.get(name)
|
|
4378
|
+
if (staleDrive) {
|
|
4379
|
+
this.#drives.delete(name)
|
|
4380
|
+
await staleDrive.close().catch(() => {})
|
|
4381
|
+
}
|
|
4382
|
+
this.#drivePromises.delete(name)
|
|
4383
|
+
return this.#getOrCreateDrive(name, options)
|
|
4384
|
+
}
|
|
4385
|
+
|
|
4386
|
+
async #writeDriveFile(drive, driveKey, content, cleanPath) {
|
|
4387
|
+
const ws = drive.createWriteStream(driveKey)
|
|
4388
|
+
|
|
4389
|
+
if (Buffer.isBuffer(content)) {
|
|
4390
|
+
let offset = 0
|
|
4391
|
+
const waitForDrain = () =>
|
|
4392
|
+
new Promise(resolve => ws.once('drain', resolve))
|
|
4393
|
+
|
|
4394
|
+
try {
|
|
4395
|
+
while (offset < content.length) {
|
|
4396
|
+
const chunk = content.slice(offset, offset + FILE_WRITE_CHUNK_SIZE)
|
|
4397
|
+
const canContinue = ws.write(chunk)
|
|
4398
|
+
offset += chunk.length
|
|
4399
|
+
if (!canContinue && offset < content.length) {
|
|
4400
|
+
await waitForDrain()
|
|
4401
|
+
}
|
|
4402
|
+
}
|
|
4403
|
+
ws.end()
|
|
4404
|
+
await new Promise((resolve, reject) => {
|
|
4405
|
+
ws.on('finish', resolve)
|
|
4406
|
+
ws.on('error', reject)
|
|
4407
|
+
})
|
|
4408
|
+
} catch (err) {
|
|
4409
|
+
ws.destroy()
|
|
4410
|
+
throw err
|
|
4411
|
+
}
|
|
4412
|
+
return
|
|
4413
|
+
}
|
|
4414
|
+
|
|
4415
|
+
const rs = fs.createReadStream(cleanPath)
|
|
4416
|
+
try {
|
|
4417
|
+
await new Promise((resolve, reject) => {
|
|
4418
|
+
rs.pipe(ws)
|
|
4419
|
+
ws.on('finish', resolve)
|
|
4420
|
+
ws.on('error', reject)
|
|
4421
|
+
rs.on('error', reject)
|
|
4422
|
+
})
|
|
4423
|
+
} catch (err) {
|
|
4424
|
+
ws.destroy()
|
|
4425
|
+
rs.destroy()
|
|
4426
|
+
throw err
|
|
4427
|
+
}
|
|
4428
|
+
}
|
|
4429
|
+
|
|
4315
4430
|
#getOwnerKey(ownerAddress) {
|
|
4316
4431
|
return getOwnerBucketKey(ownerAddress)
|
|
4317
4432
|
}
|