react-native-email-imap-smtp 0.3.29 → 0.3.31

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.
@@ -47,7 +47,10 @@ data class ConnectionConfig(
47
47
  val checkCertificate: Boolean,
48
48
  @DoNotStrip
49
49
  @Keep
50
- val debug: Boolean
50
+ val debug: Boolean,
51
+ @DoNotStrip
52
+ @Keep
53
+ val downloadChunkSize: Double?
51
54
  ) {
52
55
  /* primary constructor */
53
56
 
@@ -64,6 +67,7 @@ data class ConnectionConfig(
64
67
  && Objects.deepEquals(this.timeout, other.timeout)
65
68
  && Objects.deepEquals(this.checkCertificate, other.checkCertificate)
66
69
  && Objects.deepEquals(this.debug, other.debug)
70
+ && Objects.deepEquals(this.downloadChunkSize, other.downloadChunkSize)
67
71
  }
68
72
 
69
73
  override fun hashCode(): Int {
@@ -77,7 +81,8 @@ data class ConnectionConfig(
77
81
  accessToken,
78
82
  timeout,
79
83
  checkCertificate,
80
- debug
84
+ debug,
85
+ downloadChunkSize
81
86
  ).contentDeepHashCode()
82
87
  }
83
88
 
@@ -89,8 +94,8 @@ data class ConnectionConfig(
89
94
  @Keep
90
95
  @Suppress("unused")
91
96
  @JvmStatic
92
- private fun fromCpp(host: String, port: Double, username: String, password: String, tls: Boolean, authType: String, accessToken: String?, timeout: Double, checkCertificate: Boolean, debug: Boolean): ConnectionConfig {
93
- return ConnectionConfig(host, port, username, password, tls, authType, accessToken, timeout, checkCertificate, debug)
97
+ private fun fromCpp(host: String, port: Double, username: String, password: String, tls: Boolean, authType: String, accessToken: String?, timeout: Double, checkCertificate: Boolean, debug: Boolean, downloadChunkSize: Double?): ConnectionConfig {
98
+ return ConnectionConfig(host, port, username, password, tls, authType, accessToken, timeout, checkCertificate, debug, downloadChunkSize)
94
99
  }
95
100
  }
96
101
  }
@@ -217,6 +217,21 @@ namespace margelo::nitro::emailimapsmtp::bridge::swift {
217
217
  return Func_void_std__exception_ptr_Wrapper(std::move(value));
218
218
  }
219
219
 
220
+ // pragma MARK: std::optional<double>
221
+ /**
222
+ * Specialized version of `std::optional<double>`.
223
+ */
224
+ using std__optional_double_ = std::optional<double>;
225
+ inline std::optional<double> create_std__optional_double_(const double& value) noexcept {
226
+ return std::optional<double>(value);
227
+ }
228
+ inline bool has_value_std__optional_double_(const std::optional<double>& optional) noexcept {
229
+ return optional.has_value();
230
+ }
231
+ inline double get_std__optional_double_(const std::optional<double>& optional) noexcept {
232
+ return optional.value();
233
+ }
234
+
220
235
  // pragma MARK: std::shared_ptr<Promise<void>>
221
236
  /**
222
237
  * Specialized version of `std::shared_ptr<Promise<void>>`.
@@ -467,21 +482,6 @@ namespace margelo::nitro::emailimapsmtp::bridge::swift {
467
482
  return Func_void_std__vector_EmailMessage__Wrapper(std::move(value));
468
483
  }
469
484
 
470
- // pragma MARK: std::optional<double>
471
- /**
472
- * Specialized version of `std::optional<double>`.
473
- */
474
- using std__optional_double_ = std::optional<double>;
475
- inline std::optional<double> create_std__optional_double_(const double& value) noexcept {
476
- return std::optional<double>(value);
477
- }
478
- inline bool has_value_std__optional_double_(const std::optional<double>& optional) noexcept {
479
- return optional.has_value();
480
- }
481
- inline double get_std__optional_double_(const std::optional<double>& optional) noexcept {
482
- return optional.value();
483
- }
484
-
485
485
  // pragma MARK: std::variant<nitro::NullType, EmailMessage>
486
486
  /**
487
487
  * Wrapper struct for `std::variant<nitro::NullType, EmailMessage>`.
@@ -18,14 +18,20 @@ public extension ConnectionConfig {
18
18
  /**
19
19
  * Create a new instance of `ConnectionConfig`.
20
20
  */
21
- init(host: String, port: Double, username: String, password: String, tls: Bool, authType: String, accessToken: String?, timeout: Double, checkCertificate: Bool, debug: Bool) {
21
+ init(host: String, port: Double, username: String, password: String, tls: Bool, authType: String, accessToken: String?, timeout: Double, checkCertificate: Bool, debug: Bool, downloadChunkSize: Double?) {
22
22
  self.init(std.string(host), port, std.string(username), std.string(password), tls, std.string(authType), { () -> bridge.std__optional_std__string_ in
23
23
  if let __unwrappedValue = accessToken {
24
24
  return bridge.create_std__optional_std__string_(std.string(__unwrappedValue))
25
25
  } else {
26
26
  return .init()
27
27
  }
28
- }(), timeout, checkCertificate, debug)
28
+ }(), timeout, checkCertificate, debug, { () -> bridge.std__optional_double_ in
29
+ if let __unwrappedValue = downloadChunkSize {
30
+ return bridge.create_std__optional_double_(__unwrappedValue)
31
+ } else {
32
+ return .init()
33
+ }
34
+ }())
29
35
  }
30
36
 
31
37
  @inline(__always)
@@ -84,4 +90,16 @@ public extension ConnectionConfig {
84
90
  var debug: Bool {
85
91
  return self.__debug
86
92
  }
93
+
94
+ @inline(__always)
95
+ var downloadChunkSize: Double? {
96
+ return { () -> Double? in
97
+ if bridge.has_value_std__optional_double_(self.__downloadChunkSize) {
98
+ let __unwrapped = bridge.get_std__optional_double_(self.__downloadChunkSize)
99
+ return __unwrapped
100
+ } else {
101
+ return nil
102
+ }
103
+ }()
104
+ }
87
105
  }
@@ -50,10 +50,11 @@ namespace margelo::nitro::emailimapsmtp {
50
50
  double timeout SWIFT_PRIVATE;
51
51
  bool checkCertificate SWIFT_PRIVATE;
52
52
  bool debug SWIFT_PRIVATE;
53
+ std::optional<double> downloadChunkSize SWIFT_PRIVATE;
53
54
 
54
55
  public:
55
56
  ConnectionConfig() = default;
56
- explicit ConnectionConfig(std::string host, double port, std::string username, std::string password, bool tls, std::string authType, std::optional<std::string> accessToken, double timeout, bool checkCertificate, bool debug): host(host), port(port), username(username), password(password), tls(tls), authType(authType), accessToken(accessToken), timeout(timeout), checkCertificate(checkCertificate), debug(debug) {}
57
+ explicit ConnectionConfig(std::string host, double port, std::string username, std::string password, bool tls, std::string authType, std::optional<std::string> accessToken, double timeout, bool checkCertificate, bool debug, std::optional<double> downloadChunkSize): host(host), port(port), username(username), password(password), tls(tls), authType(authType), accessToken(accessToken), timeout(timeout), checkCertificate(checkCertificate), debug(debug), downloadChunkSize(downloadChunkSize) {}
57
58
 
58
59
  public:
59
60
  friend bool operator==(const ConnectionConfig& lhs, const ConnectionConfig& rhs) = default;
@@ -78,7 +79,8 @@ namespace margelo::nitro {
78
79
  JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "accessToken"))),
79
80
  JSIConverter<double>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "timeout"))),
80
81
  JSIConverter<bool>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "checkCertificate"))),
81
- JSIConverter<bool>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "debug")))
82
+ JSIConverter<bool>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "debug"))),
83
+ JSIConverter<std::optional<double>>::fromJSI(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "downloadChunkSize")))
82
84
  );
83
85
  }
84
86
  static inline jsi::Value toJSI(jsi::Runtime& runtime, const margelo::nitro::emailimapsmtp::ConnectionConfig& arg) {
@@ -93,6 +95,7 @@ namespace margelo::nitro {
93
95
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "timeout"), JSIConverter<double>::toJSI(runtime, arg.timeout));
94
96
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "checkCertificate"), JSIConverter<bool>::toJSI(runtime, arg.checkCertificate));
95
97
  obj.setProperty(runtime, PropNameIDCache::get(runtime, "debug"), JSIConverter<bool>::toJSI(runtime, arg.debug));
98
+ obj.setProperty(runtime, PropNameIDCache::get(runtime, "downloadChunkSize"), JSIConverter<std::optional<double>>::toJSI(runtime, arg.downloadChunkSize));
96
99
  return obj;
97
100
  }
98
101
  static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
@@ -113,6 +116,7 @@ namespace margelo::nitro {
113
116
  if (!JSIConverter<double>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "timeout")))) return false;
114
117
  if (!JSIConverter<bool>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "checkCertificate")))) return false;
115
118
  if (!JSIConverter<bool>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "debug")))) return false;
119
+ if (!JSIConverter<std::optional<double>>::canConvert(runtime, obj.getProperty(runtime, PropNameIDCache::get(runtime, "downloadChunkSize")))) return false;
116
120
  return true;
117
121
  }
118
122
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-email-imap-smtp",
3
- "version": "0.3.29",
3
+ "version": "0.3.31",
4
4
  "description": "The best of imap and smtp client of react native",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -1 +1 @@
1
- {"version":3,"file":"imapHandler.d.ts","sourceRoot":"","sources":["../src/imapHandler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"imapHandler.d.ts","sourceRoot":"","sources":["../src/imapHandler.ts"],"names":[],"mappings":"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"}
@@ -366,12 +366,57 @@ async function unsubscribeFolder(sessionId, folderName) {
366
366
  await s.client.mailboxUnsubscribe(folderName);
367
367
  }
368
368
  // ─── 获取邮件 ──────────────────────────────────────────────
369
+ /** 从 bodyStructure 推导 text/plain 与 text/html 的 part key 与字符集(与 fetchAllAttachments 的 walk 同构) */
370
+ function findTextParts(bs) {
371
+ const out = {};
372
+ function walk(node, prefix) {
373
+ if (node.childNodes && node.childNodes.length > 0) {
374
+ node.childNodes.forEach((child, i) => {
375
+ walk(child, prefix ? `${prefix}.${i + 1}` : `${i + 1}`);
376
+ });
377
+ return;
378
+ }
379
+ const type = String(node.type || '').toLowerCase();
380
+ const sub = String(node.subType || '').toLowerCase();
381
+ const charset = node.parameters?.charset;
382
+ if (type === 'text' && sub === 'plain' && !out.plain) {
383
+ out.plain = { key: prefix || '1', charset };
384
+ }
385
+ else if (type === 'text' && sub === 'html' && !out.html) {
386
+ out.html = { key: prefix || '1', charset };
387
+ }
388
+ }
389
+ walk(bs, '');
390
+ return out;
391
+ }
392
+ /** 解码 text part 内容(传输编码已由 imapflow 解码;这里处理字符集,GBK 等用 iconv-lite) */
393
+ function decodePartText(buf, charset) {
394
+ const cs = String(charset || 'utf-8').toLowerCase().replace(/"/g, '').trim();
395
+ try {
396
+ const iconv = require('iconv-lite');
397
+ if (iconv.encodingExists(cs))
398
+ return iconv.decode(buf, cs);
399
+ }
400
+ catch {
401
+ /* 降级到 Buffer.toString */
402
+ }
403
+ try {
404
+ return buf.toString(cs);
405
+ }
406
+ catch {
407
+ return buf.toString('utf-8');
408
+ }
409
+ }
369
410
  async function fetchEmails(sessionId, options) {
370
411
  const s = getSession(sessionId);
371
412
  const mailbox = options.mailbox ?? 'INBOX';
372
413
  const lock = await s.client.getMailboxLock(mailbox);
373
414
  try {
374
- const mbox = await s.client.mailboxOpen(mailbox);
415
+ // 已选中同一邮箱时跳过 SELECT,避免重复往返
416
+ const alreadySelected = s.client.mailbox && s.client.mailbox.path === mailbox;
417
+ const mbox = alreadySelected
418
+ ? s.client.mailbox
419
+ : await s.client.mailboxOpen(mailbox);
375
420
  s.currentMailbox = mailbox;
376
421
  const total = mbox.exists ?? 0;
377
422
  // 构建查询范围
@@ -417,7 +462,8 @@ async function fetchEmails(sessionId, options) {
417
462
  df.before = new Date(options.before);
418
463
  range = { ...range, ...df };
419
464
  }
420
- // 构建 fetch fields
465
+ // 构建 fetch fields(默认不拉全文;正文用第二段只取 text 部分,跳过附件下载)
466
+ const needBody = options.fetchBody !== false;
421
467
  const fields = {
422
468
  uid: true,
423
469
  flags: true,
@@ -427,19 +473,57 @@ async function fetchEmails(sessionId, options) {
427
473
  internalDate: true,
428
474
  headers: true,
429
475
  };
430
- if (options.fetchBody !== false) {
431
- fields.source = true;
432
- }
433
- const messages = [];
476
+ // 第一段:元数据(含 bodyStructure),随后推导正文 part key
477
+ const entries = [];
478
+ const byUid = new Map();
434
479
  let seq = 0;
480
+ let textParts = null;
435
481
  for await (const msg of s.client.fetch(range, fields, fetchOpts)) {
436
482
  seq++;
437
- let body;
438
- if (msg.source) {
439
- body = await parseSource(msg.source);
483
+ const entry = { msg, seq };
484
+ entries.push(entry);
485
+ byUid.set(msg.uid, entry);
486
+ if (needBody && !textParts && msg.bodyStructure) {
487
+ textParts = findTextParts(msg.bodyStructure);
440
488
  }
441
- messages.push(buildEmailObject(msg, seq, body));
442
489
  }
490
+ // 第二段:只取 text/plain 与 text/html 正文(不下载附件)
491
+ if (needBody && textParts && (textParts.plain || textParts.html)) {
492
+ const bodyParts = [];
493
+ if (textParts.plain)
494
+ bodyParts.push({ key: textParts.plain.key });
495
+ if (textParts.html)
496
+ bodyParts.push({ key: textParts.html.key });
497
+ for await (const b of s.client.fetch(range, { uid: true, bodyParts }, fetchOpts)) {
498
+ const entry = byUid.get(b.uid);
499
+ if (entry && b.bodyParts) {
500
+ entry.bodyPartsMap = b.bodyParts;
501
+ }
502
+ }
503
+ }
504
+ const messages = entries.map(({ msg, seq: seqNo, bodyPartsMap }) => {
505
+ let body;
506
+ if (needBody && bodyPartsMap && textParts) {
507
+ const getPart = (key) => bodyPartsMap.get(key) ?? bodyPartsMap.get(key.toUpperCase());
508
+ if (textParts.plain) {
509
+ const buf = getPart(textParts.plain.key);
510
+ if (buf)
511
+ body = {
512
+ ...(body ?? {}),
513
+ text: decodePartText(buf, textParts.plain.charset),
514
+ };
515
+ }
516
+ if (textParts.html) {
517
+ const buf = getPart(textParts.html.key);
518
+ if (buf)
519
+ body = {
520
+ ...(body ?? {}),
521
+ html: decodePartText(buf, textParts.html.charset),
522
+ };
523
+ }
524
+ }
525
+ return buildEmailObject(msg, seqNo, body);
526
+ });
443
527
  messages.sort((a, b) => b.uid - a.uid);
444
528
  return messages;
445
529
  }
@@ -471,7 +555,11 @@ async function searchEmails(sessionId, criteria) {
471
555
  const mailbox = criteria.mailbox ?? 'INBOX';
472
556
  const lock = await s.client.getMailboxLock(mailbox);
473
557
  try {
474
- await s.client.mailboxOpen(mailbox);
558
+ // 已选中同一邮箱时跳过 SELECT
559
+ const alreadySelected = s.client.mailbox && s.client.mailbox.path === mailbox;
560
+ if (!alreadySelected) {
561
+ await s.client.mailboxOpen(mailbox);
562
+ }
475
563
  const searchQuery = {};
476
564
  if (criteria.text)
477
565
  searchQuery.text = criteria.text;
@@ -529,6 +617,8 @@ async function searchEmails(sessionId, criteria) {
529
617
  if (criteria.limit && criteria.limit > 0) {
530
618
  uids = uids.slice(0, criteria.limit);
531
619
  }
620
+ // 只取元数据 + 只拉 text 正文(不下载附件),与 fetchEmails 一致
621
+ const needBody = criteria.fetchBody !== false;
532
622
  const fields = {
533
623
  uid: true,
534
624
  flags: true,
@@ -538,15 +628,57 @@ async function searchEmails(sessionId, criteria) {
538
628
  internalDate: true,
539
629
  headers: true,
540
630
  };
541
- if (criteria.fetchBody !== false)
542
- fields.source = true;
543
- const messages = [];
631
+ // 第一段:元数据(含 bodyStructure),随后推导正文 part key
632
+ const entries = [];
633
+ const byUid = new Map();
544
634
  let seq = 0;
635
+ let textParts = null;
545
636
  for await (const msg of s.client.fetch({ uid: uids.join(',') }, fields, { uid: true })) {
546
637
  seq++;
547
- const body = msg.source ? await parseSource(msg.source) : undefined;
548
- messages.push(buildEmailObject(msg, seq, body));
638
+ const entry = { msg, seq };
639
+ entries.push(entry);
640
+ byUid.set(msg.uid, entry);
641
+ if (needBody && !textParts && msg.bodyStructure) {
642
+ textParts = findTextParts(msg.bodyStructure);
643
+ }
644
+ }
645
+ // 第二段:只取 text/plain 与 text/html 正文(不下载附件)
646
+ if (needBody && textParts && (textParts.plain || textParts.html)) {
647
+ const bodyParts = [];
648
+ if (textParts.plain)
649
+ bodyParts.push({ key: textParts.plain.key });
650
+ if (textParts.html)
651
+ bodyParts.push({ key: textParts.html.key });
652
+ for await (const b of s.client.fetch({ uid: uids.join(',') }, { uid: true, bodyParts }, { uid: true })) {
653
+ const entry = byUid.get(b.uid);
654
+ if (entry && b.bodyParts) {
655
+ entry.bodyPartsMap = b.bodyParts;
656
+ }
657
+ }
549
658
  }
659
+ const messages = entries.map(({ msg, seq: seqNo, bodyPartsMap }) => {
660
+ let body;
661
+ if (needBody && bodyPartsMap && textParts) {
662
+ const getPart = (key) => bodyPartsMap.get(key) ?? bodyPartsMap.get(key.toUpperCase());
663
+ if (textParts.plain) {
664
+ const buf = getPart(textParts.plain.key);
665
+ if (buf)
666
+ body = {
667
+ ...(body ?? {}),
668
+ text: decodePartText(buf, textParts.plain.charset),
669
+ };
670
+ }
671
+ if (textParts.html) {
672
+ const buf = getPart(textParts.html.key);
673
+ if (buf)
674
+ body = {
675
+ ...(body ?? {}),
676
+ html: decodePartText(buf, textParts.html.charset),
677
+ };
678
+ }
679
+ }
680
+ return buildEmailObject(msg, seqNo, body);
681
+ });
550
682
  return messages;
551
683
  }
552
684
  finally {
@@ -556,58 +688,48 @@ async function searchEmails(sessionId, criteria) {
556
688
  // ─── 标记操作 ──────────────────────────────────────────────
557
689
  async function markAsRead(sessionId, uids, silent) {
558
690
  const s = getSession(sessionId);
559
- for (const uid of uids) {
560
- await s.client.messageFlagsAdd({ uid: String(uid) }, ['\\Seen'], {
561
- silent,
562
- uid: true,
563
- });
564
- }
691
+ // 批量:一条 range 命令完成所有 UID,避免逐封往返
692
+ await s.client.messageFlagsAdd({ uid: uids.join(',') }, ['\\Seen'], {
693
+ silent,
694
+ uid: true,
695
+ });
565
696
  }
566
697
  async function markAsFlagged(sessionId, uids, flagged) {
567
698
  const s = getSession(sessionId);
568
- for (const uid of uids) {
569
- if (flagged) {
570
- await s.client.messageFlagsAdd({ uid: String(uid) }, ['\\Flagged'], {
571
- uid: true,
572
- });
573
- }
574
- else {
575
- await s.client.messageFlagsRemove({ uid: String(uid) }, ['\\Flagged'], {
576
- uid: true,
577
- });
578
- }
699
+ const range = { uid: uids.join(',') };
700
+ if (flagged) {
701
+ await s.client.messageFlagsAdd(range, ['\\Flagged'], { uid: true });
702
+ }
703
+ else {
704
+ await s.client.messageFlagsRemove(range, ['\\Flagged'], { uid: true });
579
705
  }
580
706
  }
581
707
  async function moveEmails(sessionId, uids, destinationMailbox) {
582
708
  const s = getSession(sessionId);
583
- for (const uid of uids) {
584
- await s.client.messageMove({ uid: String(uid) }, destinationMailbox, {
585
- uid: true,
586
- });
587
- }
709
+ await s.client.messageMove({ uid: uids.join(',') }, destinationMailbox, {
710
+ uid: true,
711
+ });
588
712
  }
589
713
  async function copyEmails(sessionId, uids, destinationMailbox) {
590
714
  const s = getSession(sessionId);
591
- for (const uid of uids) {
592
- await s.client.messageCopy({ uid: String(uid) }, destinationMailbox, {
593
- uid: true,
594
- });
595
- }
715
+ await s.client.messageCopy({ uid: uids.join(',') }, destinationMailbox, {
716
+ uid: true,
717
+ });
596
718
  }
597
719
  async function deleteEmails(sessionId, uids) {
598
720
  const s = getSession(sessionId);
599
- const success = [];
600
- const failed = [];
601
- for (const uid of uids) {
602
- try {
603
- await s.client.messageDelete({ uid: String(uid) }, { uid: true });
604
- success.push(uid);
605
- }
606
- catch {
607
- failed.push(uid);
608
- }
721
+ // 批量:一次 messageDelete 完成;失败则整批计入 failed(不再逐封往返)
722
+ try {
723
+ await s.client.messageDelete({ uid: uids.join(',') }, { uid: true });
724
+ return { success: uids, failed: [] };
725
+ }
726
+ catch (err) {
727
+ return {
728
+ success: [],
729
+ failed: uids,
730
+ error: err?.message ?? 'Delete failed',
731
+ };
609
732
  }
610
- return { success, failed };
611
733
  }
612
734
  async function expunge(sessionId) {
613
735
  const s = getSession(sessionId);
@@ -677,7 +799,9 @@ async function fetchAttachment(sessionId, uid, partId) {
677
799
  // 同时降低被 20s 超时误判的风险。不要设到接近无上限——imapflow 靠
678
800
  // 「块是否被填满」(chunk.length >= chunkSize) 判断是否还有更多,
679
801
  // 服务器若截断超大单次响应,会被误判为已结束 → 附件被静默截断。
680
- const download = await withTimeout(s.client.download(String(uid), partId, { uid: true, chunkSize: 1024 * 1024 }), 20000, `download ${partId}`);
802
+ // 分块大小默认 1MB,可由连接配置 downloadChunkSize 覆盖(与 Android 对齐)
803
+ const chunkSize = s.config?.downloadChunkSize ?? 1024 * 1024;
804
+ const download = await withTimeout(s.client.download(String(uid), partId, { uid: true, chunkSize }), 20000, `download ${partId}`);
681
805
  const chunks = [];
682
806
  for await (const chunk of download.content) {
683
807
  chunks.push(chunk);