validate.js
1.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* 投递前基本校验(纯函数,零网络)。
* 只做「畸形请求拦截」:非法邮箱、空 subject。
* 不做业务规则(验证码、退订等不属于本包)。
*/
import { EmailError } from './map-error.js'
// 务实的邮箱形态校验:localpart@domain.tld,不含空白,domain 至少一个点。
// 不追求 RFC 5322 全量正确(那是投递通道的事),只拦明显畸形。
const EMAIL_RE = /^[^\s@"']+@[^\s@.]+(\.[^\s@.]+)+$/
/**
* @param {string} addr
* @returns {boolean}
*/
export function isValidEmail(addr) {
return typeof addr === 'string' && addr.length <= 254 && EMAIL_RE.test(addr)
}
/**
* 校验一条 SendMessage 的收件人与 subject。非法即抛 EmailError。
* @param {import('./index.js').SendMessage} msg
* @param {string} provider 用于错误归属
* @returns {{ to: string|string[] }}
*/
export function assertSendable(msg, provider) {
if (!msg || typeof msg !== 'object') {
throw new EmailError({ provider, reason: 'invalidRecipient', retryable: false, message: 'message must be an object' })
}
const recipients = msg.to == null ? [] : (Array.isArray(msg.to) ? msg.to : [msg.to])
if (recipients.length === 0) {
throw new EmailError({ provider, reason: 'invalidRecipient', retryable: false, message: 'no recipient' })
}
for (const r of recipients) {
if (!isValidEmail(r)) {
throw new EmailError({
provider,
reason: 'invalidRecipient',
retryable: false,
message: `invalid recipient address: ${String(r)}`,
})
}
}
if (typeof msg.subject !== 'string' || msg.subject.trim() === '') {
throw new EmailError({ provider, reason: 'invalidRecipient', retryable: false, message: 'empty subject' })
}
if ((msg.html == null || msg.html === '') && (msg.text == null || msg.text === '')) {
throw new EmailError({ provider, reason: 'invalidRecipient', retryable: false, message: 'message has no html or text body' })
}
return { to: msg.to }
}