@seayoo-web/messenger
v3.0.2
Published
agent for messenger
Readme
@seayoo-web/messenger
消息系统 messenger 的 Node.js SDK。
两种消息:
- 定向消息(
PersonalMessage)—— 发给指定的人,用 API Key 鉴权。 - 主题消息(
TopicMessage)—— 发到一个主题,由主题的关注者收取,用主题密钥鉴权。
pnpm add @seayoo-web/messenger要求 Node.js >= 22。这是服务端 SDK,在浏览器里 import 会直接抛错。
定向消息
API Key 在 messenger.seayoo.com 的「我的密钥」里签发,签发时填写的「来源」会记进调用统计。
import { PersonalMessage } from "@seayoo-web/messenger";
const messenger = new PersonalMessage(process.env.MESSENGER_API_KEY!, "order-service", "1.2.3");
await messenger.send({
to: "[email protected]",
title: "订单异常",
content: {
订单号: "SO-20260920-001",
金额: 128,
状态: "支付超时",
},
payload: "请尽快处理",
});send(options)
| 字段 | 类型 | 说明 |
| --------- | --------------------------------------------------------------------------- | -------------------------------------------------------------- |
| to | string \| string[] | 接收人邮箱,必填 |
| title | string | 消息标题 |
| content | string \| string[] \| Record<string, string \| number \| boolean \| null> | 消息内容,见下文 |
| payload | string? | 附加内容,显示在正文下方 |
| style | "default" \| "warning" \| "info" \| "error" | 消息样式 |
| enhance | "key" \| "value" \| "kv" \| "none" | 加粗强调哪一部分,仅当 content 不是字符串时有效 |
| sender | string? | 信使的 AppId(向系统管理员查询),不填或找不到时由默认信使发送 |
主题消息
主题 ID 和主题密钥在主题的管理页面里获取。
import { TopicMessage } from "@seayoo-web/messenger";
const topic = new TopicMessage(topicId, topicSecretKey, "order-service", "1.2.3");
await topic.publish({
title: "每日对账完成",
content: ["共 1024 笔", "差异 0 笔"],
keywords: ["对账"],
});publish(options)
字段与 send 相同,但没有 to 和 sender,多一个 keywords:
| 字段 | 类型 | 说明 |
| ---------- | ----------- | -------------------------------------------- |
| keywords | string[]? | 消息关键词。关注者可以只订阅自己关心的关键词 |
管理关注者
// 查询关注列表(不含群聊)
const followers = await topic.queryFollower();
// [{ email, name, followTime, status: "wait" | "follow", keywords }]
// 添加关注
await topic.addFollower({ email: "[email protected]", name: "张三", keywords: ["对账"] });
// 移除关注
await topic.removeFollower("[email protected]");queryFollower 失败时返回空数组,不抛错;其余方法见下面的「错误处理」。
消息内容的三种写法
content 支持三种形式:
// 一段纯文本
content: "今天的对账已完成";
// 多行文本
content: ["共 1024 笔", "差异 0 笔"];
// 键值对,会渲染成两列;值不是字符串时自动转成字符串
content: { 订单号: "SO-001", 金额: 128, 已支付: true };键值对里,把键或值写成 "-" 会渲染成一条分隔线:
content: { 订单号: "SO-001", "-": "-", 金额: 128 };声明调用方
两个客户端的构造函数都接受可选的调用方名字和版本,会带进 User-Agent:
new PersonalMessage(apiKey, "order-service");
new PersonalMessage(apiKey, "order-service", "1.2.3");
new TopicMessage(topicId, topicSecretKey, "order-service", "1.2.3");发出的 User-Agent 形如:
messenger-node-sdk/3.0.2 (order-service/1.2.3; node22.13.0; linux/x64)服务端每个请求都会记一行日志,userAgent 就在里面,排查问题时可以直接按调用方筛。不传则只带 SDK 自己的信息。
SDK 版本也单独导出,方便打进自己的启动日志:
import { sdkVersion } from "@seayoo-web/messenger";错误处理
send / publish / addFollower / removeFollower 成功时 resolve true,失败一律抛错,不会返回 false。
参数不合法时抛普通 Error(MissingApiKey、MissingTarget、MissingContent、EmailFormatError);请求失败时抛 MessengerError,它继承自 Error,额外带着服务端的错误码、这次请求的追踪编号和 HTTP 状态码:
import { traceIdOf } from "@seayoo-web/messenger";
try {
await messenger.send({ to, title, content });
} catch (err) {
// 反馈问题时带上 traceId,服务端按它能查到这次请求的完整日志
logger.error("发送失败", { err, traceId: traceIdOf(err) });
}traceIdOf 按字段取值而不是用 instanceof——ESM 与 CJS 两份构建混用时 instanceof 不成立。请求没发出去(连不上、超时)时 status 为 0。
按错误码分支
message 是给人看的描述,要分辨错误类型请用 code,取值见 errorCode:ApiKeyMissing、ApiKeyInvalid、ApiKeyRevoked、ParamInvalid、ParamMissing、Forbidden、FollowLimitReached、UnexpectedError。响应体不是 { code, message } 结构时(网关等中间环节)code 是空串。
API Key 没通过校验时服务端返回 401,三种原因分开给。它们都得人来处置,重试没有意义:
import { isApiKeyRejected, codeOf, errorCode } from "@seayoo-web/messenger";
try {
await messenger.send({ to, title, content });
} catch (err) {
if (isApiKeyRejected(err)) {
// 缺失、无效或者已被吊销
alert.page("messenger 的 API Key 不可用", { code: codeOf(err) });
}
if (codeOf(err) === errorCode.apiKeyRevoked) {
// 只想区分「被管理员吊销」时
}
}codeOf 和 traceIdOf 一样按字段取值。注意 Node 自己的错误也常带 code(ECONNREFUSED 这类),拿到非空值不代表它来自 messenger 服务端,判断时要和 errorCode 里的值比对。
追踪编号
SDK 每个请求都会带上 X-Trace-Id,服务端沿用这个编号记日志并在响应头里带回来,两边的日志就能按同一个编号串起来。
默认每个请求自己生成一个。如果你的服务已经有请求上下文,设置一次钩子,SDK 就会从你的上下文里取:
import { setTraceIdSource } from "@seayoo-web/messenger";
// 比如用 AsyncLocalStorage 保存请求上下文
setTraceIdSource(() => store.getStore()?.traceId);取不到或格式不合规(服务端要求 16–64 位的字母、数字、下划线或短横线)时,SDK 自己生成一个;钩子抛错不影响发消息。传 null 取消。
编号格式可以用 isValidTraceId 自行校验。
从 2.x 升级
3.0.0 跟着服务端统一了 API Key 的用词,两处不兼容:
- API Key 没通过校验时的判别标识从
message挪到了code。 2.x 里服务端返回的code恒为Unauthorized,标识写在message里,叫ServiceTokenMissing/ServiceTokenError/ServiceTokenRevoked;现在code就是ApiKeyMissing/ApiKeyInvalid/ApiKeyRevoked,message换成了中文描述。原先按message匹配的代码要改成isApiKeyRejected(err)或者比对codeOf(err)。状态码仍是 401。 - 没传 API Key 时抛的
MissingToken改名为MissingApiKey。
请求头仍然是 x-msg-token,构造函数的签名也没变,只是参数名在文档里改叫 apiKey。
MessengerError 新增了 code 字段,另外新增 errorCode、codeOf、isApiKeyRejected 三个导出——这些都是加法,不影响现有代码。
