@flun/mailer 2.1.23 → 2.1.25

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.
@@ -7,25 +7,25 @@ import {
7
7
  PK, osHostname, isIP, netConnect, regexs, resetRegex, getLogger, resolveHostname, callbackPromise, encodeXText, cleanup
8
8
  } from '../shared.js';
9
9
 
10
- // 默认超时(连接,套接字无活动,等待SMTP问候,DNS解析)时间(毫秒)
10
+ // 默认超时(连接,套接字无活动,等待SMTP问候,DNS解析)时间(毫秒)
11
11
  const CONNECTION_TIMEOUT = 2 * 60 * 1000, SOCKET_TIMEOUT = 10 * 60 * 1000,
12
12
  GREETING_TIMEOUT = 30 * 1000, DNS_TIMEOUT = 30 * 1000;
13
13
 
14
14
  /**
15
15
  * 生成SMTP连接对象;
16
16
  * >查看定义:@see {@link SmtpConnection}
17
- * 可选的选项对象可以包含以下属性:
18
- * - **port** - 连接的端口(默认为587或465)
19
- * - **host** - 连接的主机名或IP地址(默认为'localhost')
17
+ * 可选的选项对象可以包含以下属性:
18
+ * - **port** - 连接的端口(默认为587或465)
19
+ * - **host** - 连接的主机名或IP地址(默认为'localhost')
20
20
  * - **secure** - 使用SSL
21
21
  * - **ignoreTLS** - 忽略服务器对STARTTLS的支持
22
22
  * - **requireTLS** - 强制客户端使用STARTTLS
23
23
  * - **name** - 客户端服务器名称
24
24
  * - **localAddress** - 绑定的出站地址
25
- * - **greetingTimeout** - 等待服务器问候消息的超时时间(毫秒)
26
- * - **connectionTimeout** - 等待连接建立的超时时间(毫秒)
27
- * - **socketTimeout** - 连接关闭前的无活动超时时间(默认为1小时)
28
- * - **dnsTimeout** - 等待DNS请求解析的超时时间(默认为30秒)
25
+ * - **greetingTimeout** - 等待服务器问候消息的超时时间(毫秒)
26
+ * - **connectionTimeout** - 等待连接建立的超时时间(毫秒)
27
+ * - **socketTimeout** - 连接关闭前的无活动超时时间(默认为1小时)
28
+ * - **dnsTimeout** - 等待DNS请求解析的超时时间(默认为30秒)
29
29
  * - **lmtp** - 如果为true,使用LMTP而不是SMTP协议
30
30
  * - **logger** - bunyan兼容的日志接口
31
31
  * - **debug** - 如果为true,将SMTP流量传递给日志记录器
@@ -51,7 +51,7 @@ class SmtpConnection extends EventEmitter {
51
51
  this.servername = servername ? servername : !isIP(this.host) ? this.host : false;
52
52
  this.allowInternalNetworkInterfaces = allowInternalNetworkInterfaces;
53
53
 
54
- // 如果secure选项未设置但端口是465,则默认为安全连接
54
+ // 如果secure选项未设置但端口是465,则默认为安全连接
55
55
  if (typeof secure === 'undefined' && this.port === 465) this.secureConnection = true;
56
56
  this.name = name || this.#getHostname();
57
57
  this.logger = getLogger(this.options, { component, sid: this.id }); // 设置日志记录器
@@ -64,7 +64,7 @@ class SmtpConnection extends EventEmitter {
64
64
  this.customAuth.set(mapKey, customAuth[key]);
65
65
  });
66
66
 
67
- this.secure = !!this.secureConnection; // 定义当前连接是否安全。如果不安全,如果服务器支持,可以使用STARTTLS
67
+ this.secure = !!this.secureConnection; // 定义当前连接是否安全;如果不安全,如果服务器支持,可以使用STARTTLS
68
68
  this.version = PK.version; // 暴露版本号
69
69
  this._remainder = ''; // 存储来自服务器的不完整消息
70
70
  this._maxAllowedSize = 0; // 定义单个消息允许的最大大小
@@ -73,16 +73,16 @@ class SmtpConnection extends EventEmitter {
73
73
  this._recipientQueue = []; // 这个队列包含要发送的收件人列表
74
74
  this._responseQueue = []; // 来自服务器的未处理响应队列
75
75
  this._supportedAuth = []; // 列出支持的认证机制列表
76
- this._socket = false; // 连接到服务器的套接字,如果没有套接字,则为false
77
- this._closing = false; // 如果套接字正在关闭,则设置为true
78
- this._envelope = false; // 包含当前信封(发件人,收件人)的对象,如果没有信封,则为false
79
- this._destroyed = false; // 如果套接字被认为已关闭,则设置为true
80
- this._greetingTimeout = false; // 等待问候消息的超时变量,如果为true,则表示已超时
81
- this._connectionTimeout = false; // 等待连接开始的超时变量,如果为true,则表示已超时
82
- this.destroyed = false; // 如果设置为true,此实例不再活跃
83
- this.allowsAuth = false; // 如果EHLO响应包含"AUTH",则设置为true。如果为false,则不尝试认证
84
- this.authenticated = false; // 如果为true,则表示用户已认证
85
- this.lastServerResponse = false; // 最后的服务器响应,如果没有响应,则为false
76
+ this._socket = false; // 连接到服务器的套接字,如果没有套接字,则为false
77
+ this._closing = false; // 如果套接字正在关闭,则设置为true
78
+ this._envelope = false; // 包含当前信封(发件人,收件人)的对象,如果没有信封,则为false
79
+ this._destroyed = false; // 如果套接字被认为已关闭,则设置为true
80
+ this._greetingTimeout = false; // 等待问候消息的超时变量,如果为true,则表示已超时
81
+ this._connectionTimeout = false; // 等待连接开始的超时变量,如果为true,则表示已超时
82
+ this.destroyed = false; // 如果设置为true,此实例不再活跃
83
+ this.allowsAuth = false; // 如果EHLO响应包含"AUTH",则设置为true;如果为false,则不尝试认证
84
+ this.authenticated = false; // 如果为true,则表示用户已认证
85
+ this.lastServerResponse = false; // 最后的服务器响应,如果没有响应,则为false
86
86
 
87
87
  /**
88
88
  * 套接字监听器的回调函数
@@ -151,13 +151,14 @@ class SmtpConnection extends EventEmitter {
151
151
  };
152
152
  if (localAddress) opts.localAddress = localAddress;
153
153
 
154
- // 如果已提供连接对象,则使用它
154
+ // 如果已提供连接对象,则使用它
155
155
  if (connection) {
156
156
  this._socket = connection, this.#setupConnectionHandlers();
157
157
  if (this.secureConnection && !this.alreadySecured)
158
158
  setImmediate(() =>
159
159
  this.#upgradeConnection(err => {
160
- if (err) return this.#onError(new Error(`启动TLS错误-${(err.message || err)}`), 'ETLS', false, 'CONN');
160
+ if (err) return this.#onError(new Error(`启动TLS错误-${(err.message || err)}`), 'ETLS', false,
161
+ 'CONN');
161
162
  this.#onConnect();
162
163
  })
163
164
  );
@@ -173,13 +174,13 @@ class SmtpConnection extends EventEmitter {
173
174
  this._socket.setKeepAlive(true), this._socket.once('connect', () => this.#onConnect());
174
175
  });
175
176
  };
176
- // 如果套接字已设置,则使用它
177
+ // 如果套接字已设置,则使用它
177
178
  if (socket)
178
179
  return createConnection((resolvedOpts) => {
179
180
  socket.connect(this.port, this.host, () => { });
180
181
  return socket;
181
182
  });
182
- // 如果secureConnection为true,则使用TLS连接
183
+ // 如果secureConnection为true,则使用TLS连接
183
184
  else if (this.secureConnection) {
184
185
  if (tls) Object.keys(tls).forEach(key => { opts[key] = tls[key]; })
185
186
  if (this.servername && !opts.servername) opts.servername = this.servername; // 确保SNI的服务器名称
@@ -232,12 +233,12 @@ class SmtpConnection extends EventEmitter {
232
233
 
233
234
  const { method = '', oauth2, credentials, user, pass, options } = this._auth = authData;
234
235
  this._authMethod = method.toString().trim().toUpperCase(); // 选择SASL认证方法
235
- // 如果没有指定认证方法,则使用XOAUTH2认证,如果没有OAuth2凭据,则使用PLAIN认证
236
+ // 如果没有指定认证方法,则使用XOAUTH2认证,如果没有OAuth2凭据,则使用PLAIN认证
236
237
  if (!this._authMethod && oauth2 && !credentials) this._authMethod = 'XOAUTH2';
237
238
  else if (!this._authMethod || (this._authMethod === 'XOAUTH2' && !oauth2))
238
239
  this._authMethod = (this._supportedAuth[0] || 'PLAIN').toUpperCase().trim();
239
240
 
240
- // 如果没有指定凭据,则使用用户名和密码
241
+ // 如果没有指定凭据,则使用用户名和密码
241
242
  if (this._authMethod !== 'XOAUTH2' && (!credentials || !credentials.user || !credentials.pass)) {
242
243
  if ((user && pass) || this.customAuth.has(this._authMethod)) this._auth.credentials = { user, pass, options };
243
244
  else return callback(this.#formatError(`缺少 "${this._authMethod}" 的凭据`, 'EAUTH', false, 'API'));
@@ -262,7 +263,8 @@ class SmtpConnection extends EventEmitter {
262
263
  extensions: [].concat(this._supportedExtensions), authMethods: [].concat(this._supportedAuth),
263
264
  maxAllowedSize: this._maxAllowedSize || false,
264
265
  sendCommand: (cmd, done) => {
265
- const promise = !done ? new Promise((resolve, reject) => done = callbackPromise(resolve, reject)) : null;
266
+ const promise = !done ? new Promise((resolve, reject) => done = callbackPromise(resolve, reject)) :
267
+ null;
266
268
  this._responseActions.push(str => {
267
269
  lastResponse = str;
268
270
  const codes = str.match(regexs.RESPONSE_CODE), data = { command: cmd, response: str };
@@ -279,8 +281,9 @@ class SmtpConnection extends EventEmitter {
279
281
  }, resolve, reject
280
282
  });
281
283
 
282
- // 如果返回了Promise,则等待Promise完成
283
- if (handlerResponse && typeof handlerResponse.catch === 'function') handlerResponse.then(resolve).catch(reject);
284
+ // 如果返回了Promise,则等待Promise完成
285
+ if (handlerResponse &&
286
+ typeof handlerResponse.catch === 'function') handlerResponse.then(resolve).catch(reject);
284
287
  return;
285
288
  }
286
289
 
@@ -288,7 +291,8 @@ class SmtpConnection extends EventEmitter {
288
291
  case 'XOAUTH2':
289
292
  return this.#actionAUTH_XOAUTH2_TOKEN(false, callback);
290
293
  case 'LOGIN':
291
- this._responseActions.push(str => this.#actionAUTH_LOGIN_USER(str, callback)), this.#sendCommand('AUTH LOGIN');
294
+ this._responseActions.push(str => this.#actionAUTH_LOGIN_USER(str, callback)),
295
+ this.#sendCommand('AUTH LOGIN');
292
296
  return;
293
297
  case 'PLAIN':
294
298
  this._responseActions.push(str => this.#actionAUTH_PLAIN_RESPONSE(str, callback));
@@ -307,7 +311,7 @@ class SmtpConnection extends EventEmitter {
307
311
  /**
308
312
  * 发送消息
309
313
  *
310
- * @param {Object} envelope 信封对象,{from: addr, to: [addr]}
314
+ * @param {Object} envelope 信封对象,{from: addr, to: [addr]}
311
315
  * @param {Object} message 字符串、缓冲区或流
312
316
  * @param {Function} callback 发送完成后调用的回调函数
313
317
  */
@@ -324,7 +328,8 @@ class SmtpConnection extends EventEmitter {
324
328
  });
325
329
 
326
330
  const state = { returned: false }, callback = (...args) => { cleanup(state), done(...args) };
327
- if (typeof message.on === 'function') message.on('error', err => callback(this.#formatError(err, 'ESTREAM', false, 'API')));
331
+ if (typeof message.on === 'function') message.on('error', err => callback(this.#formatError(err, 'ESTREAM', false,
332
+ 'API')));
328
333
 
329
334
  // 将消息写入流
330
335
  const writeMessageToStream = stream => {
@@ -355,7 +360,8 @@ class SmtpConnection extends EventEmitter {
355
360
  reset(callback) {
356
361
  this.#sendCommand('RSET');
357
362
  this._responseActions.push(str => {
358
- if (!str.startsWith('2')) return callback(this.#formatError(`无法重置会话状态;响应=${str}`, 'EPROTOCOL', str, 'RSET'));
363
+ if (!str.startsWith('2')) return callback(this.#formatError(`无法重置会话状态;响应=${str}`, 'EPROTOCOL', str,
364
+ 'RSET'));
359
365
  this._envelope = false;
360
366
  return callback(null, true);
361
367
  });
@@ -372,7 +378,7 @@ class SmtpConnection extends EventEmitter {
372
378
  this.logger.info({ tnx: 'network', localAddress, localPort, remoteAddress, remotePort },
373
379
  '%s 已建立到 %s:%s', this.secure ? '安全连接' : '连接', remoteAddress, remotePort
374
380
  );
375
- if (this._destroyed) return this.close(); // 如果连接已销毁,则关闭连接
381
+ if (this._destroyed) return this.close(); // 如果连接已销毁,则关闭连接
376
382
 
377
383
  this.stage = 'connected';
378
384
  // 清除套接字的现有监听器
@@ -383,7 +389,7 @@ class SmtpConnection extends EventEmitter {
383
389
  .setTimeout(this.options.socketTimeout || SOCKET_TIMEOUT).on('timeout', this.onSocketTimeout);
384
390
 
385
391
  this._greetingTimeout = setTimeout(() => {
386
- // 如果仍在等待问候消息,则放弃
392
+ // 如果仍在等待问候消息,则放弃
387
393
  if (this._socket && !this._destroyed && this._responseActions[0] === this.#actionGreeting)
388
394
  this.#onError('从未收到问候消息', 'ETIMEDOUT', false, 'CONN');
389
395
 
@@ -406,13 +412,14 @@ class SmtpConnection extends EventEmitter {
406
412
  let lastline = rQueue.at(-1) ?? null;
407
413
  for (let i = 0; i < lines.length; i++) {
408
414
  const line = lines[i];
409
- // 检查是否需要延续(确保 lastline 存在)
415
+ // 检查是否需要延续(确保 lastline 存在)
410
416
  if (lastline && regexs.CONTINUATION_RESPONSE.test(lastline.split('\n').pop()))
411
417
  lastline += `\n${line}`, rQueue[rQueue.length - 1] = lastline;
412
- else rQueue.push(line), lastline = line; // 新响应:推入队列并更新 lastline
418
+ else rQueue.push(line), lastline = line; // 新响应:推入队列并更新 lastline
413
419
  }
414
420
 
415
- if (lastline && regexs.CONTINUATION_RESPONSE.test(lastline.split('\n').pop())) return; // 如果响应未完成,等待后续数据
421
+ if (lastline &&
422
+ regexs.CONTINUATION_RESPONSE.test(lastline.split('\n').pop())) return; // 如果响应未完成,等待后续数据
416
423
  this.#processResponse(); // 处理完整响应
417
424
  }
418
425
 
@@ -425,7 +432,7 @@ class SmtpConnection extends EventEmitter {
425
432
  */
426
433
  #onError(err, type, data, command) {
427
434
  clearTimeout(this._connectionTimeout), clearTimeout(this._greetingTimeout);
428
- if (this._destroyed) return; // 如果连接已被销毁,则忽略错误
435
+ if (this._destroyed) return; // 如果连接已被销毁,则忽略错误
429
436
 
430
437
  err = this.#formatError(err, type, data, command);
431
438
  this.logger.error(data, err.message), this.emit('error', err), this.close();
@@ -452,12 +459,14 @@ class SmtpConnection extends EventEmitter {
452
459
  let serverResponse = false;
453
460
  const _remainder = this._remainder, { debug, transactionLog } = this.options;
454
461
  if (_remainder?.trim()) {
455
- if (debug || transactionLog) this.logger.debug({ tnx: 'server' }, _remainder.replace(regexs.OPTIONAL_CRLF, ''));
462
+ if (debug || transactionLog) this.logger.debug({ tnx: 'server' }, _remainder.replace(regexs.OPTIONAL_CRLF,
463
+ ''));
456
464
  this.lastServerResponse = serverResponse = _remainder.trim();
457
465
  }
458
466
 
459
467
  this.logger.info({ tnx: 'network' }, '连接已关闭');
460
- if (this.upgrading && !this._destroyed) return this.#onError(new Error('连接意外关闭'), 'ETLS', serverResponse, 'CONN');
468
+ if (this.upgrading && !this._destroyed) return this.#onError(new Error('连接意外关闭'), 'ETLS', serverResponse,
469
+ 'CONN');
461
470
  else if ((![this.#actionGreeting, this.close].includes(this._responseActions[0]) && !this._destroyed) ||
462
471
  regexs.RESPONSE_4XX_5XX.test(serverResponse))
463
472
  return this.#onError(new Error('连接意外关闭'), 'ECONNECTION', serverResponse, 'CONN');
@@ -484,7 +493,7 @@ class SmtpConnection extends EventEmitter {
484
493
  }
485
494
 
486
495
  /**
487
- * 销毁客户端,发出'end'事件
496
+ * 销毁客户端,发出'end'事件
488
497
  */
489
498
  #destroy() {
490
499
  if (this._destroyed) return;
@@ -521,13 +530,13 @@ class SmtpConnection extends EventEmitter {
521
530
  this.upgrading = false;
522
531
  return callback(err);
523
532
  }
524
- socketPlain.resume(); // 如果套接字暂停,则恢复
533
+ socketPlain.resume(); // 如果套接字暂停,则恢复
525
534
  }
526
535
 
527
536
  /**
528
537
  * 处理来自服务器的排队响应
529
538
  *
530
- * @param {Boolean} force 如果为true,忽略_processing标志
539
+ * @param {Boolean} force 如果为true,忽略_processing标志
531
540
  */
532
541
  #processResponse() {
533
542
  if (!this._responseQueue.length) return false;
@@ -545,13 +554,13 @@ class SmtpConnection extends EventEmitter {
545
554
  }
546
555
 
547
556
  /**
548
- * 向服务器发送命令,附加\r\n
557
+ * 向服务器发送命令,附加\r\n
549
558
  *
550
559
  * @param {String} str 要发送到服务器的字符串
551
- * @param {String} logStr 用于记录的可选字符串,而不是实际字符串
560
+ * @param {String} logStr 用于记录的可选字符串,而不是实际字符串
552
561
  */
553
562
  #sendCommand(str = '', logStr) {
554
- if (this._destroyed) return; // 连接已关闭,无法发送更多数据
563
+ if (this._destroyed) return; // 连接已关闭,无法发送更多数据
555
564
  if (this._socket.destroyed) return this.close();
556
565
  if (this.options.debug || this.options.transactionLog)
557
566
  this.logger.debug({ tnx: 'client' }, (logStr || str).toString().replace(regexs.OPTIONAL_CRLF, ''));
@@ -560,9 +569,9 @@ class SmtpConnection extends EventEmitter {
560
569
  }
561
570
 
562
571
  /**
563
- * 通过提交信封数据来初始化新消息,以MAIL FROM:命令开始
572
+ * 通过提交信封数据来初始化新消息,以MAIL FROM:命令开始
564
573
  *
565
- * @param {Object} envelope 信封对象,形式为 {from:'...', to:['...']} 或
574
+ * @param {Object} envelope 信封对象,形式为 {from:'...', to:['...']} 或
566
575
  * {from:{address:'...',name:'...'}, to:[address:'...',name:'...']}
567
576
  */
568
577
  #setEnvelope(envelope = {}, callback) {
@@ -579,7 +588,8 @@ class SmtpConnection extends EventEmitter {
579
588
  // 验证单个地址并检测非ASCII字符
580
589
  const _validateAddress = (address, type) => {
581
590
  if (regexs.INVALID_ADDRESS_CHARS.test(address)) errors.push(`无效的${type}: ${JSON.stringify(address)}`);
582
- else if (regexs.NON_ASCII.test(address)) useSmtpUtf8 = true; // 检查地址是否使用ASCII字符,否则需使用SMTPUTF8扩展
591
+ // 检查地址是否使用ASCII字符,否则需使用SMTPUTF8扩展
592
+ else if (regexs.NON_ASCII.test(address)) useSmtpUtf8 = true;
583
593
  };
584
594
  _validateAddress(from, '发件人');
585
595
  to.forEach(addr => _validateAddress(addr, '收件人'));
@@ -599,11 +609,13 @@ class SmtpConnection extends EventEmitter {
599
609
 
600
610
  this._responseActions.push(str => this.#actionMAIL(str, callback));
601
611
 
602
- // 如果服务器支持SMTPUTF8并且信封包含国际化电子邮件地址,则将SMTPUTF8关键字附加到MAIL FROM命令
603
- if (useSmtpUtf8 && this._supportedExtensions.includes('SMTPUTF8')) args.push('SMTPUTF8'), this._usingSmtpUtf8 = true;
612
+ // 如果服务器支持SMTPUTF8并且信封包含国际化电子邮件地址,则将SMTPUTF8关键字附加到MAIL FROM命令
613
+ if (useSmtpUtf8 && this._supportedExtensions.includes('SMTPUTF8')) args.push('SMTPUTF8'),
614
+ this._usingSmtpUtf8 = true;
604
615
 
605
616
  // 如果服务器支持8BITMIME并且消息可能包含非ASCII字节,则将8BITMIME关键字附加到MAIL FROM命令
606
- if (use8BitMime && this._supportedExtensions.includes('8BITMIME')) args.push('BODY=8BITMIME'), this._using8BitMime = true;
617
+ if (use8BitMime && this._supportedExtensions.includes('8BITMIME')) args.push('BODY=8BITMIME'),
618
+ this._using8BitMime = true;
607
619
 
608
620
  if (size && this._supportedExtensions.includes('SIZE')) args.push(`SIZE=${size}`);
609
621
 
@@ -701,8 +713,8 @@ class SmtpConnection extends EventEmitter {
701
713
  }
702
714
 
703
715
  /**
704
- * 在连接创建后且服务器发送问候消息后运行。
705
- * 如果传入消息以220开头,则通过发送EHLO命令启动SMTP会话
716
+ * 在连接创建后且服务器发送问候消息后运行;
717
+ * 如果传入消息以220开头,则通过发送EHLO命令启动SMTP会话
706
718
  *
707
719
  * @param {String} str 来自服务器的消息
708
720
  */
@@ -713,8 +725,8 @@ class SmtpConnection extends EventEmitter {
713
725
  }
714
726
 
715
727
  /**
716
- * 处理LHLO命令的服务器响应。如果产生错误,
717
- * 发出'error',否则将其视为EHLO响应
728
+ * 处理LHLO命令的服务器响应;如果产生错误,
729
+ * 发出'error',否则将其视为EHLO响应
718
730
  *
719
731
  * @param {String} str 来自服务器的消息
720
732
  */
@@ -724,14 +736,15 @@ class SmtpConnection extends EventEmitter {
724
736
  }
725
737
 
726
738
  /**
727
- * 处理EHLO命令的服务器响应。如果产生错误,
728
- * 尝试改用HELO,否则如果服务器支持STARTTLS
729
- * 则启动TLS协商,或进入认证阶段。
739
+ * 处理EHLO命令的服务器响应;如果产生错误,
740
+ * 尝试改用HELO,否则如果服务器支持STARTTLS
741
+ * 则启动TLS协商,或进入认证阶段;
730
742
  *
731
743
  * @param {String} str 来自服务器的消息
732
744
  */
733
745
  #actionEHLO(str) {
734
- if (str.startsWith('421')) return this.#onError(new Error(`服务器终止连接;响应=${str}`), 'ECONNECTION', str, 'EHLO');
746
+ if (str.startsWith('421')) return this.#onError(new Error(`服务器终止连接;响应=${str}`), 'ECONNECTION', str,
747
+ 'EHLO');
735
748
 
736
749
  // 通用命令队列函数
737
750
  const queueCommand = (action, command) => {
@@ -739,7 +752,8 @@ class SmtpConnection extends EventEmitter {
739
752
  }, { requireTLS, ignoreTLS } = this.options;
740
753
  if (!str.startsWith('2')) {
741
754
  if (requireTLS)
742
- return this.#onError(new Error(`EHLO失败但HELO不支持必需的STARTTLS;响应=${str}`), 'ECONNECTION', str, 'EHLO');
755
+ return this.#onError(new Error(`EHLO失败但HELO不支持必需的STARTTLS;响应=${str}`), 'ECONNECTION', str,
756
+ 'EHLO');
743
757
  return queueCommand(this.#actionHELO, `HELO ${this.name}`); // 使用通用函数发送 HELO
744
758
  }
745
759
 
@@ -762,14 +776,14 @@ class SmtpConnection extends EventEmitter {
762
776
  if (new RegExp(`${authMechanismSource}CRAM-MD5`, 'i').test(str)) this._supportedAuth.push('CRAM-MD5');
763
777
  if (new RegExp(`${authMechanismSource}XOAUTH2`, 'i').test(str)) this._supportedAuth.push('XOAUTH2');
764
778
 
765
- // 检测服务器是否支持SIZE扩展(以及最大允许大小)
779
+ // 检测服务器是否支持SIZE扩展(以及最大允许大小)
766
780
  if (match) this._supportedExtensions.push('SIZE'), this._maxAllowedSize = Number(match[1]) || 0;
767
781
  this.emit('connect');
768
782
  }
769
783
 
770
784
  /**
771
- * 处理HELO命令的服务器响应。如果产生错误,
772
- * 发出'error',否则进入认证阶段。
785
+ * 处理HELO命令的服务器响应;如果产生错误,
786
+ * 发出'error',否则进入认证阶段;
773
787
  *
774
788
  * @param {String} str 来自服务器的消息
775
789
  */
@@ -779,15 +793,15 @@ class SmtpConnection extends EventEmitter {
779
793
  }
780
794
 
781
795
  /**
782
- * 处理STARTTLS命令的服务器响应。如果有错误
783
- * 尝试改用HELO,否则启动TLS升级。如果升级成功,重新启动EHLO
796
+ * 处理STARTTLS命令的服务器响应;如果有错误
797
+ * 尝试改用HELO,否则启动TLS升级;如果升级成功,重新启动EHLO
784
798
  *
785
799
  * @param {String} str 来自服务器的消息
786
800
  */
787
801
  #actionSTARTTLS(str) {
788
802
  if (!str.startsWith('2')) {
789
803
  if (this.options.opportunisticTLS) {
790
- this.logger.info({ tnx: 'smtp' }, 'STARTTLS升级失败,继续未加密');
804
+ this.logger.info({ tnx: 'smtp' }, 'STARTTLS升级失败,继续未加密');
791
805
  return this.emit('connect');
792
806
  }
793
807
  return this.#onError(new Error('使用STARTTLS升级连接时出错'), 'ETLS', str, 'STARTTLS');
@@ -797,14 +811,14 @@ class SmtpConnection extends EventEmitter {
797
811
  if (err) return this.#onError(new Error(`启动TLS错误-${err.message || err}`), 'ETLS', false, 'STARTTLS');
798
812
 
799
813
  this.logger.info({ tnx: 'smtp' }, '连接已使用STARTTLS升级');
800
- secured ? this.#sendHeloCommand() : this.emit('connect');// 如果升级成功,重新启动会话,否则继续未加密
814
+ secured ? this.#sendHeloCommand() : this.emit('connect');// 如果升级成功,重新启动会话,否则继续未加密
801
815
  });
802
816
  }
803
817
 
804
818
  /**
805
819
  * 处理AUTH LOGIN用户名认证阶段
806
- * 预期收到服务器334响应(Base64编码的"Username:"提示)
807
- * 发送Base64编码的用户名,并准备处理密码认证
820
+ * 预期收到服务器334响应(Base64编码的"Username:"提示)
821
+ * 发送Base64编码的用户名,并准备处理密码认证
808
822
  *
809
823
  * @param {String} str 服务器响应消息
810
824
  * @param {Function} callback 认证结果回调
@@ -820,16 +834,17 @@ class SmtpConnection extends EventEmitter {
820
834
  /**
821
835
  * 处理AUTH CRAM-MD5命令:
822
836
  * 1. 服务器发送一个随机字符串
823
- * 2. 客户端用密码作为密钥,对其进行HMAC-MD5加密
837
+ * 2. 客户端用密码作为密钥,对其进行HMAC-MD5加密
824
838
  * 3. 将"用户名 加密结果"进行base64编码后返回给服务器
825
839
  *
826
- * @param {String} str 服务器发送的消息,格式:334 <base64编码的随机字符串>
840
+ * @param {String} str 服务器发送的消息,格式:334 <base64编码的随机字符串>
827
841
  * @param {Function} callback 认证结果回调函数
828
842
  */
829
843
  #actionAUTH_CRAM_MD5(str, callback) {
830
844
  const challengeMatch = str.match(regexs.CRAM_MD5_CHALLENGE);
831
845
  let challengeString = '';
832
- if (!challengeMatch) return callback(this.#formatError('在等待服务器响应时登录序列无效', 'EAUTH', str, 'AUTH CRAM-MD5'));
846
+ if (!challengeMatch) return callback(this.#formatError('在等待服务器响应时登录序列无效', 'EAUTH', str,
847
+ 'AUTH CRAM-MD5'));
833
848
  else challengeString = challengeMatch[1];
834
849
 
835
850
  // 从base64解码
@@ -840,12 +855,13 @@ class SmtpConnection extends EventEmitter {
840
855
  const prepended = `${user} ${hmacMD5.digest('hex')}`;
841
856
 
842
857
  this._responseActions.push(str => this.#actionAUTH_CRAM_MD5_PASS(str, callback));
843
- this.#sendCommand(Buffer.from(prepended).toString('base64'), Buffer.from(`${user} /* secret */`).toString('base64'));
858
+ this.#sendCommand(Buffer.from(prepended).toString('base64'),
859
+ Buffer.from(`${user} /* secret */`).toString('base64'));
844
860
  }
845
861
 
846
862
  /**
847
- * 处理CRAM-MD5认证的响应,如果没有错误,
848
- * 用户可以视为已登录。开始等待要发送的消息
863
+ * 处理CRAM-MD5认证的响应,如果没有错误,
864
+ * 用户可以视为已登录;开始等待要发送的消息
849
865
  *
850
866
  * @param {String} str 来自服务器的消息
851
867
  */
@@ -862,7 +878,7 @@ class SmtpConnection extends EventEmitter {
862
878
 
863
879
  /**
864
880
  * 处理AUTH LOGIN命令的响应
865
- * 以'334 UGFzc3dvcmQ6'('Password:'的base64编码)作为响应发送的数据
881
+ * 以'334 UGFzc3dvcmQ6'('Password:'的base64编码)作为响应发送的数据
866
882
  *
867
883
  * @param {String} str 来自服务器的消息
868
884
  */
@@ -878,8 +894,8 @@ class SmtpConnection extends EventEmitter {
878
894
  }
879
895
 
880
896
  /**
881
- * 处理认证的响应,如果没有错误,
882
- * 用户可以视为已登录。开始等待要发送的消息
897
+ * 处理认证的响应,如果没有错误,
898
+ * 用户可以视为已登录;开始等待要发送的消息
883
899
  *
884
900
  * @param {String} str 来自服务器的消息
885
901
  */
@@ -902,7 +918,8 @@ class SmtpConnection extends EventEmitter {
902
918
  return callback(this.#formatError('无效的登录', 'EAUTH', str, `AUTH ${method}`));
903
919
  }
904
920
 
905
- this.logger.info({ tnx: 'smtp', username: user, action: 'authenticated', method }, '用户 %s 已认证', JSON.stringify(user));
921
+ this.logger.info({ tnx: 'smtp', username: user, action: 'authenticated', method }, '用户 %s 已认证',
922
+ JSON.stringify(user));
906
923
  this.authenticated = true, callback(null, true);
907
924
  }
908
925
 
@@ -927,9 +944,9 @@ class SmtpConnection extends EventEmitter {
927
944
  this.#sendCommand(`RCPT TO:<${recipient}>${this.#getDsnRcptToArgs()}`);
928
945
  };
929
946
 
930
- // 流水线模式:一次性发送所有收件人
947
+ // 流水线模式:一次性发送所有收件人
931
948
  if (this._supportedExtensions.includes('PIPELINING')) while (rcptQueue.length) processRecipient(rcptQueue.shift());
932
- else processRecipient(rcptQueue.shift()); // 非流水线模式:逐个发送收件
949
+ else processRecipient(rcptQueue.shift()); // 非流水线模式:逐个发送收件
933
950
  }
934
951
 
935
952
  /**
@@ -971,7 +988,7 @@ class SmtpConnection extends EventEmitter {
971
988
  * @param {String} str 来自服务器的消息
972
989
  */
973
990
  #actionDATA(str, callback) {
974
- // 如果不是23,则数据命令失败
991
+ // 如果不是23,则数据命令失败
975
992
  if (!regexs.DATA_RESPONSE.test(str)) return callback(this.#formatError('数据命令失败', 'EENVELOPE', str, 'DATA'));
976
993
  const { accepted, rejected, rejectedErrors } = this._envelope, response = { accepted, rejected };
977
994
 
@@ -986,7 +1003,8 @@ class SmtpConnection extends EventEmitter {
986
1003
  * @param {String} str 来自服务器的消息
987
1004
  */
988
1005
  #actionSMTPStream(str, callback) {
989
- return str.startsWith('2') ? callback(null, str) : callback(this.#formatError('消息失败', 'EMESSAGE', str, 'DATA'));
1006
+ return str.startsWith('2') ? callback(null, str) : callback(this.#formatError('消息失败', 'EMESSAGE', str,
1007
+ 'DATA'));
990
1008
  }
991
1009
 
992
1010
  /**
@@ -1007,7 +1025,7 @@ class SmtpConnection extends EventEmitter {
1007
1025
  return callback(err);
1008
1026
  }
1009
1027
 
1010
- return callback(null, final ? str : null); // 成功情况:总是调用回调
1028
+ return callback(null, final ? str : null); // 成功情况:总是调用回调
1011
1029
  }
1012
1030
 
1013
1031
  // 处理_XOAUTH2认证