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.
Files changed (53) hide show
  1. package/out/about/index.html +2 -2
  2. package/out/admin/index.html +0 -0
  3. package/out/app/index.html +0 -0
  4. package/out/assets/{AccountMenu-D-wa5tux.js → AccountMenu-B26qus-Q.js} +1 -1
  5. package/out/assets/{AppShell-BdRKZIZ9.js → AppShell-BiYQsNO8.js} +1 -1
  6. package/out/assets/{AppTop-BE5kKBYH.js → AppTop-D-qYC0ep.js} +1 -1
  7. package/out/assets/ChatRestoringIndicator-CLV78gNW.js +1 -0
  8. package/out/assets/{CopyButton-DnFqfCN9.js → CopyButton-BrEkzqEq.js} +1 -1
  9. package/out/assets/{MarketingHeader-8gNzHawY.js → MarketingHeader-CJXpogPY.js} +1 -1
  10. package/out/assets/{MarketingLayout-BPdjpEfc.js → MarketingLayout-Ch4sw5Qm.js} +1 -1
  11. package/out/assets/{MilkdownEditor-C388llV6.js → MilkdownEditor-DfhuT9eU.js} +1 -1
  12. package/out/assets/{OpenSidebarButton-OP8zl7ad.js → OpenSidebarButton-Bvwr7B6k.js} +1 -1
  13. package/out/assets/{chatJoinInvite-CeC6H6t8.js → chatJoinInvite-7tuD-STm.js} +1 -1
  14. package/out/assets/i18n-DKsg67mH.js +1 -0
  15. package/out/assets/index-FEwag7ej.css +1 -0
  16. package/out/assets/{index-D1mozno-.js → index-mjFWLwue.js} +3 -3
  17. package/out/assets/{index.lazy-B1iJsXQ8.js → index.lazy-8GSHLIGt.js} +1 -1
  18. package/out/assets/{index.lazy-CX2l1_Gi.js → index.lazy-B3jH9yBA.js} +1 -1
  19. package/out/assets/index.lazy-B5JMiv3z.js +1 -0
  20. package/out/assets/{index.lazy-CVFcovqX.js → index.lazy-BAr7-dhf.js} +1 -1
  21. package/out/assets/index.lazy-BMFwyE_Z.js +3 -0
  22. package/out/assets/{index.lazy-Bz77Jlzu.js → index.lazy-BbA_1LnY.js} +1 -1
  23. package/out/assets/{index.lazy-m9XAzMt1.js → index.lazy-BmpOpQqg.js} +1 -1
  24. package/out/assets/{index.lazy-FvXAYdEO.js → index.lazy-CB3C6kMj.js} +1 -1
  25. package/out/assets/{index.lazy-ClrSkRx5.js → index.lazy-CVOAOO0_.js} +1 -1
  26. package/out/assets/{index.lazy-CflWbocP.js → index.lazy-CyFSwJuo.js} +1 -1
  27. package/out/assets/{index.lazy-DzaN9qwA.js → index.lazy-D3iEq2ZW.js} +2 -2
  28. package/out/assets/{index.lazy-CsTaY5WS.js → index.lazy-F3iHN_Ao.js} +1 -1
  29. package/out/assets/{index.lazy-D7E1pb1S.js → index.lazy-PnZR3z1V.js} +1 -1
  30. package/out/assets/{index.lazy-59kmyNMW.js → index.lazy-qDpRLqXu.js} +1 -1
  31. package/out/assets/{index.lazy-C_J9afOY.js → index.lazy-s6Ok77GI.js} +1 -1
  32. package/out/assets/{saveLocalFile-2CNt4X0T.js → saveLocalFile-BLTn3Z18.js} +1 -1
  33. package/out/assets/useChannelMessages-DKlO_2oW.js +4 -0
  34. package/out/assets/{useGameRoom-asr2gyY6.js → useGameRoom-DLN636mz.js} +1 -1
  35. package/out/chat/index.html +0 -0
  36. package/out/chat/join/demo/index.html +0 -0
  37. package/out/chat/join/index.html +0 -0
  38. package/out/download/index.html +2 -2
  39. package/out/game/gandengyan/index.html +0 -0
  40. package/out/game/index.html +0 -0
  41. package/out/game/zhajinhua/index.html +0 -0
  42. package/out/index.html +2 -2
  43. package/out/note/index.html +0 -0
  44. package/out/ping/index.html +2 -2
  45. package/out/profile/index.html +0 -0
  46. package/out/web3/index.html +0 -0
  47. package/package.json +1 -1
  48. package/server/src/index.js +191 -76
  49. package/out/assets/i18n-C6yaqRst.js +0 -1
  50. package/out/assets/index-C6IjeTBh.css +0 -1
  51. package/out/assets/index.lazy-CazlK1-Y.js +0 -3
  52. package/out/assets/index.lazy-JwrS2vDI.js +0 -1
  53. package/out/assets/useChannelMessages-DDBqKBXU.js +0 -4
@@ -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.#joinCidTopicInternal(cidString, {
630
- server: true,
631
- client: false,
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
- return {
641
- cid: cidString,
642
- link: buildMostLink(cidString, existing.fileName),
643
- fileName: existing.fileName,
644
- alreadyExists: true,
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
- const ws = drive.createWriteStream(driveKey)
672
-
673
- if (Buffer.isBuffer(content)) {
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
- } else {
697
- const rs = fs.createReadStream(cleanPath)
698
- await new Promise((resolve, reject) => {
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
- publishedBucket.push(fileRecord)
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
- const drive = await this.#getOrCreateDrive(
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
- try {
2244
- const entry = await drive.entry(driveKey, { wait: false })
2245
- if (!entry?.value?.blob) {
2246
- return null
2247
- }
2248
- const hasContent = await this.#hasLocalDriveContent(drive, driveKey)
2249
- if (!hasContent) {
2250
- return null
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
- const size =
2254
- Number(entry.value.blob.byteLength) ||
2255
- Number(fileRecord?.size) ||
2256
- Number(holding?.size) ||
2257
- 0
2258
- return {
2259
- drive,
2260
- entry,
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
- ownerAddress,
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
- await core.append(message)
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
  }