@zhin.js/adapter-wechat-mp 5.0.2 → 5.0.5

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,55 @@
1
1
  # @zhin.js/adapter-wechat-mp
2
2
 
3
+ ## 5.0.5
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [2d0a622]
8
+ - @zhin.js/command@1.0.10
9
+ - @zhin.js/adapter@1.1.7
10
+ - @zhin.js/core@1.5.5
11
+ - zhin.js@6.0.5
12
+
13
+ ## 5.0.4
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [0de46a8]
18
+ - Updated dependencies [c106ecc]
19
+ - Updated dependencies [ca92e03]
20
+ - Updated dependencies [b0f37ae]
21
+ - Updated dependencies [ba08a2f]
22
+ - Updated dependencies [daffd4c]
23
+ - Updated dependencies [36c7400]
24
+ - Updated dependencies [162fa34]
25
+ - Updated dependencies [e40b048]
26
+ - Updated dependencies [f1708c3]
27
+ - Updated dependencies [e53444f]
28
+ - Updated dependencies [92b0dd7]
29
+ - Updated dependencies [a7df753]
30
+ - @zhin.js/host-http@1.0.8
31
+ - @zhin.js/im-contract@1.0.3
32
+ - @zhin.js/adapter@1.1.7
33
+ - @zhin.js/plugin-runtime@1.1.5
34
+ - @zhin.js/command@1.0.9
35
+ - @zhin.js/core@1.5.4
36
+ - zhin.js@6.0.4
37
+
38
+ ## 5.0.3
39
+
40
+ ### Patch Changes
41
+
42
+ - f8c7a54: fix: im
43
+ - Updated dependencies [f8c7a54]
44
+ - @zhin.js/logger@1.0.76
45
+ - @zhin.js/host-http@1.0.7
46
+ - @zhin.js/adapter@1.1.6
47
+ - @zhin.js/command@1.0.8
48
+ - @zhin.js/core@1.5.3
49
+ - @zhin.js/im-contract@1.0.2
50
+ - @zhin.js/plugin-runtime@1.1.4
51
+ - zhin.js@6.0.3
52
+
3
53
  ## 5.0.2
4
54
 
5
55
  ### Patch Changes
@@ -20,8 +20,8 @@ export default defineAdapter({
20
20
  create(context) {
21
21
  const config = resolveWeChatMpConfig(context.config);
22
22
  // 注册到插件运行时状态(wechat-mp.endpoint list 的"运行中"数据源)
23
- context.use(wechatMpRuntimeStateToken).endpoints.set(config.name, {
24
- name: config.name,
23
+ context.use(wechatMpRuntimeStateToken).endpoints.set(config.id, {
24
+ id: config.id,
25
25
  mode: 'webhook',
26
26
  });
27
27
  return new WeChatMpEndpoint({
@@ -25,8 +25,8 @@ export default defineAdapter<WeChatMpAdapterConfig>({
25
25
  create(context) {
26
26
  const config = resolveWeChatMpConfig(context.config);
27
27
  // 注册到插件运行时状态(wechat-mp.endpoint list 的"运行中"数据源)
28
- context.use(wechatMpRuntimeStateToken).endpoints.set(config.name, {
29
- name: config.name,
28
+ context.use(wechatMpRuntimeStateToken).endpoints.set(config.id, {
29
+ id: config.id,
30
30
  mode: 'webhook',
31
31
  });
32
32
  return new WeChatMpEndpoint({
package/lib/endpoint.js CHANGED
@@ -2,12 +2,11 @@
2
2
  * WeChatMpEndpoint — lifecycle, outbound, admit, access token refresh.
3
3
  */
4
4
  import axios from 'axios';
5
- import { formatCompact, getLogger } from '@zhin.js/logger';
5
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
6
6
  import { extractOutboundText, formatCustomerServiceBody, formatInboundContent, formatInboundId, wechatMpInboundConversation, } from './protocol.js';
7
7
  import { buildMediaUploadForm, readOutboundMedia, resolveMediaBinary, } from './media-upload.js';
8
8
  import { getPassiveReplyCapture, recordPassiveReplyText, } from './passive-reply.js';
9
9
  import { registerWeChatMpWebhookRoutes } from './webhook.js';
10
- const logger = getLogger('wechat-mp');
11
10
  /** token 失效类错误码:40001/40014 invalid access_token、42001 access_token expired。 */
12
11
  const TOKEN_INVALID_ERRCODES = new Set([40001, 40014, 42001]);
13
12
  /**
@@ -29,6 +28,7 @@ function defaultFetch(url, init) {
29
28
  }).then((response) => ({ data: response.data }));
30
29
  }
31
30
  export class WeChatMpEndpoint {
31
+ #logger;
32
32
  #options;
33
33
  #fetch;
34
34
  #routeReleases = [];
@@ -42,6 +42,7 @@ export class WeChatMpEndpoint {
42
42
  #started = false;
43
43
  management = createWeChatMpEndpointManagement(this);
44
44
  constructor(options) {
45
+ this.#logger = getAdapterLogger('wechat-mp', options.config.id);
45
46
  this.#options = options;
46
47
  this.#fetch = options.fetch ?? defaultFetch;
47
48
  }
@@ -81,15 +82,15 @@ export class WeChatMpEndpoint {
81
82
  await this.#refreshAccessToken();
82
83
  this.#routeReleases.push(...registerWeChatMpWebhookRoutes(this.#options.http, this));
83
84
  this.#startTokenRefreshTimer();
84
- logger.debug(formatCompact({
85
- endpoint: this.#options.config.name,
85
+ this.#logger.debug(formatCompact({
86
+ endpoint: this.#options.config.id,
86
87
  op: 'webhook',
87
88
  path: this.#options.config.path,
88
89
  }));
89
90
  }
90
91
  catch (error) {
91
92
  await this.stop();
92
- logger.error('Failed to connect WeChat MP bot:', error);
93
+ this.#logger.error('Failed to connect WeChat MP bot:', error);
93
94
  throw error;
94
95
  }
95
96
  }
@@ -108,7 +109,7 @@ export class WeChatMpEndpoint {
108
109
  for (const release of this.#routeReleases.splice(0))
109
110
  release();
110
111
  this.#started = false;
111
- logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
112
+ this.#logger.debug(formatCompact({ op: 'disconnect' }));
112
113
  }
113
114
  async send({ conversation, payload }) {
114
115
  if (getPassiveReplyCapture()) {
@@ -119,10 +120,10 @@ export class WeChatMpEndpoint {
119
120
  if (this.#options.config.replyMode === 'customer_service') {
120
121
  return this.#sendCustomerService(conversation.id, payload);
121
122
  }
122
- logger.warn(formatCompact({
123
+ this.#logger.warn(formatCompact({
123
124
  op: 'send',
124
125
  skip: 'passive_outside_webhook',
125
- endpoint: this.#options.config.name,
126
+ endpoint: this.#options.config.id,
126
127
  target: `${conversation.kind}:${conversation.id}`,
127
128
  }));
128
129
  return `passive_skipped_${Date.now()}`;
@@ -136,15 +137,15 @@ export class WeChatMpEndpoint {
136
137
  conversation,
137
138
  message: { conversation, id: formatInboundId(msg) },
138
139
  content: formatInboundContent(msg),
139
- sender: msg.FromUserName,
140
+ sender: { id: msg.FromUserName },
141
+ endpointId: this.#options.config.id,
140
142
  metadata: Object.freeze({
141
143
  msgType: msg.MsgType,
142
144
  event: msg.Event,
143
- endpoint: this.#options.config.name,
144
145
  toUserName: msg.ToUserName,
145
146
  }),
146
147
  }).catch((err) => {
147
- logger.warn(formatCompact({
148
+ this.#logger.warn(formatCompact({
148
149
  op: 'wechat_mp_gateway_receive_failed',
149
150
  target: `${conversation.kind}:${conversation.id}`,
150
151
  error: err instanceof Error ? err.message : String(err),
@@ -161,7 +162,7 @@ export class WeChatMpEndpoint {
161
162
  let result = await this.#postCustomerService(messageData);
162
163
  if (result.errcode && TOKEN_INVALID_ERRCODES.has(Number(result.errcode))) {
163
164
  // 对端提前作废 token(多端共用等):刷新后重试一次。
164
- logger.warn(formatCompact({
165
+ this.#logger.warn(formatCompact({
165
166
  op: 'wechat_mp_token_invalid_retry',
166
167
  errcode: result.errcode,
167
168
  }));
@@ -171,7 +172,7 @@ export class WeChatMpEndpoint {
171
172
  if (result.errcode && result.errcode !== 0) {
172
173
  throw new Error(`WeChat API error: ${result.errcode} - ${result.errmsg}`);
173
174
  }
174
- logger.debug(formatCompact({ op: 'wechat_mp_send', target, messageId: result.msgid }));
175
+ this.#logger.debug(formatCompact({ op: 'wechat_mp_send', target, messageId: result.msgid }));
175
176
  return result.msgid?.toString() || `cs_${Date.now()}`;
176
177
  }
177
178
  /**
@@ -202,9 +203,9 @@ export class WeChatMpEndpoint {
202
203
  return item;
203
204
  if (typeof data.media_id === 'string' && data.media_id)
204
205
  return item;
205
- logger.warn(formatCompact({
206
+ this.#logger.warn(formatCompact({
206
207
  op: 'wechat_mp_outbound_media_dropped',
207
- endpoint: this.#options.config.name,
208
+ endpoint: this.#options.config.id,
208
209
  type: seg.type,
209
210
  reason: 'missing_media_ref',
210
211
  }));
@@ -215,9 +216,9 @@ export class WeChatMpEndpoint {
215
216
  return { type: seg.type, data: { mediaId: media.value } };
216
217
  }
217
218
  if (!uploadType) {
218
- logger.warn(formatCompact({
219
+ this.#logger.warn(formatCompact({
219
220
  op: 'wechat_mp_outbound_media_dropped',
220
- endpoint: this.#options.config.name,
221
+ endpoint: this.#options.config.id,
221
222
  type: seg.type,
222
223
  reason: 'unsupported_segment_type',
223
224
  }));
@@ -228,9 +229,9 @@ export class WeChatMpEndpoint {
228
229
  return { type: seg.type, data: { mediaId } };
229
230
  }
230
231
  catch (error) {
231
- logger.warn(formatCompact({
232
+ this.#logger.warn(formatCompact({
232
233
  op: 'wechat_mp_media_upload_failed',
233
- endpoint: this.#options.config.name,
234
+ endpoint: this.#options.config.id,
234
235
  error: error instanceof Error ? error.message : String(error),
235
236
  }));
236
237
  const alt = typeof data.alt === 'string' && data.alt ? data.alt : `[${seg.type}]`;
@@ -294,7 +295,7 @@ export class WeChatMpEndpoint {
294
295
  if (data.access_token) {
295
296
  this.#accessToken = data.access_token;
296
297
  this.#tokenExpireTime = Date.now() + (data.expires_in - 300) * 1000;
297
- logger.debug(formatCompact({ op: 'token_refresh' }));
298
+ this.#logger.debug(formatCompact({ op: 'token_refresh' }));
298
299
  return;
299
300
  }
300
301
  throw new Error(data.errmsg
@@ -305,7 +306,7 @@ export class WeChatMpEndpoint {
305
306
  this.#tokenRefreshTimer = setInterval(() => {
306
307
  if (Date.now() >= this.#tokenExpireTime) {
307
308
  void this.#refreshAccessToken().catch((error) => {
308
- logger.error('Failed to refresh access token in timer:', error);
309
+ this.#logger.error('Failed to refresh access token in timer:', error);
309
310
  });
310
311
  }
311
312
  }, 3_600_000);
package/lib/protocol.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  import type { IncomingMessage } from 'node:http';
6
6
  import type { ConversationRef } from '@zhin.js/im-contract';
7
7
  export interface WeChatMpAdapterConfig {
8
- readonly name?: string;
8
+ readonly id?: string;
9
9
  readonly appId?: string;
10
10
  readonly appSecret?: string;
11
11
  readonly token?: string;
@@ -32,7 +32,7 @@ export interface WeChatMpAdapterConfig {
32
32
  }
33
33
  export interface ResolvedWeChatMpConfig {
34
34
  readonly context: 'wechat-mp';
35
- readonly name: string;
35
+ readonly id: string;
36
36
  readonly appId: string;
37
37
  readonly appSecret: string;
38
38
  readonly token: string;
@@ -106,7 +106,7 @@ export declare function buildTextReply(wechatMsg: Pick<WeChatMessage, 'FromUserN
106
106
  * 入站归一化:公众号只有粉丝单聊场景,一律 kind='private',id=openid(FromUserName),
107
107
  * 无群/频道容器(parent 恒缺省)。
108
108
  */
109
- export declare function wechatMpInboundConversation(endpointId: string, msg: WeChatMessage): ConversationRef;
109
+ export declare function wechatMpInboundConversation(endpointKey: string, msg: WeChatMessage): ConversationRef;
110
110
  /**
111
111
  * 入站消息 id:普通消息用 MsgId;无 MsgId 的事件消息拼 Event/EventKey,
112
112
  * 避免只用秒级 CreateTime 时同秒多事件 id 碰撞。
package/lib/protocol.js CHANGED
@@ -12,8 +12,8 @@ export function resolveWeChatMpConfig(config = {}) {
12
12
  if (!appId || !appSecret || !token) {
13
13
  throw new TypeError('WeChat MP adapter requires appId + appSecret + token (plugins.<key> or endpoints with context: wechat-mp)');
14
14
  }
15
- const name = (typeof config.name === 'string' && config.name)
16
- || (typeof entry?.name === 'string' && entry.name)
15
+ const id = (typeof config.id === 'string' && config.id)
16
+ || (typeof entry?.id === 'string' && entry.id)
17
17
  || process.env.WECHAT_BOT_NAME
18
18
  || 'wechat-mp-bot';
19
19
  const path = config.path ?? entry?.path ?? '/wechat/webhook';
@@ -26,7 +26,7 @@ export function resolveWeChatMpConfig(config = {}) {
26
26
  ?? 4500;
27
27
  return {
28
28
  context: 'wechat-mp',
29
- name,
29
+ id,
30
30
  appId,
31
31
  appSecret,
32
32
  token,
@@ -174,9 +174,9 @@ export function buildTextReply(wechatMsg, content) {
174
174
  * 入站归一化:公众号只有粉丝单聊场景,一律 kind='private',id=openid(FromUserName),
175
175
  * 无群/频道容器(parent 恒缺省)。
176
176
  */
177
- export function wechatMpInboundConversation(endpointId, msg) {
177
+ export function wechatMpInboundConversation(endpointKey, msg) {
178
178
  return {
179
- endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
179
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
180
180
  kind: 'private',
181
181
  id: msg.FromUserName,
182
182
  };
package/lib/webhook.js CHANGED
@@ -138,11 +138,11 @@ export async function collectPassiveReply(handler, wechatMsg) {
138
138
  conversation,
139
139
  message: { conversation, id: formatInboundId(wechatMsg) },
140
140
  content: formatInboundContent(wechatMsg),
141
- sender: wechatMsg.FromUserName,
141
+ sender: { id: wechatMsg.FromUserName },
142
+ endpointId: handler.config.id,
142
143
  metadata: Object.freeze({
143
144
  msgType: wechatMsg.MsgType,
144
145
  event: wechatMsg.Event,
145
- endpoint: handler.config.name,
146
146
  toUserName: wechatMsg.ToUserName,
147
147
  }),
148
148
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter-wechat-mp",
3
- "version": "5.0.2",
3
+ "version": "5.0.5",
4
4
  "type": "module",
5
5
  "description": "Zhin.js WeChat Official Account adapter for Plugin Runtime (HTTP webhook)",
6
6
  "main": "./lib/index.js",
@@ -8,20 +8,20 @@
8
8
  "dependencies": {
9
9
  "axios": "^1.19.0",
10
10
  "xml2js": "^0.6.2",
11
- "@zhin.js/adapter": "1.1.5",
12
- "@zhin.js/command": "1.0.7",
13
- "@zhin.js/core": "1.5.2",
14
- "@zhin.js/host-http": "1.0.6",
15
- "@zhin.js/im-contract": "1.0.1",
16
- "@zhin.js/logger": "1.0.75",
17
- "@zhin.js/plugin-runtime": "1.1.3"
11
+ "@zhin.js/adapter": "1.1.7",
12
+ "@zhin.js/command": "1.0.10",
13
+ "@zhin.js/core": "1.5.5",
14
+ "@zhin.js/host-http": "1.0.8",
15
+ "@zhin.js/im-contract": "1.0.3",
16
+ "@zhin.js/logger": "1.0.76",
17
+ "@zhin.js/plugin-runtime": "1.1.5"
18
18
  },
19
19
  "peerDependencies": {
20
- "@zhin.js/adapter": "1.1.5",
21
- "@zhin.js/core": "1.5.2",
22
- "@zhin.js/host-http": "1.0.6",
23
- "@zhin.js/plugin-runtime": "1.1.3",
24
- "zhin.js": "6.0.2"
20
+ "@zhin.js/adapter": "1.1.7",
21
+ "@zhin.js/core": "1.5.5",
22
+ "@zhin.js/host-http": "1.0.8",
23
+ "@zhin.js/plugin-runtime": "1.1.5",
24
+ "zhin.js": "6.0.5"
25
25
  },
26
26
  "peerDependenciesMeta": {
27
27
  "zhin.js": {
package/schema.json CHANGED
@@ -32,16 +32,48 @@
32
32
  "type": "number",
33
33
  "default": 4500
34
34
  },
35
+ "master": {
36
+ "type": [
37
+ "string",
38
+ "number"
39
+ ],
40
+ "description": "框架 master(WeChat OpenID;AI/工具权限、endpoint 管理)。endpoints[i].master 可逐项覆盖"
41
+ },
42
+ "trusted": {
43
+ "type": "array",
44
+ "items": {
45
+ "type": [
46
+ "string",
47
+ "number"
48
+ ],
49
+ "description": "Trusted WeChat OpenID"
50
+ },
51
+ "description": "框架 trusted 用户列表(弱于 master)。endpoints[i].trusted 可逐项追加"
52
+ },
35
53
  "endpoints": {
36
54
  "type": "array",
37
- "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(name 必填,其余覆盖顶层)",
55
+ "description": "多账号:一个插件实例挂多个 endpoint。每项与顶层字段同构(id 必填,其余覆盖顶层)",
38
56
  "items": {
39
57
  "type": "object",
40
58
  "additionalProperties": true,
41
59
  "properties": {
42
- "name": {
43
- "type": "string",
44
- "description": "WeChat MP bot name"
60
+ "master": {
61
+ "type": [
62
+ "string",
63
+ "number"
64
+ ],
65
+ "description": "本 endpoint 的框架 master(WeChat OpenID);覆盖顶层 master"
66
+ },
67
+ "trusted": {
68
+ "type": "array",
69
+ "items": {
70
+ "type": [
71
+ "string",
72
+ "number"
73
+ ],
74
+ "description": "Trusted WeChat OpenID"
75
+ },
76
+ "description": "本 endpoint 的 trusted 列表"
45
77
  },
46
78
  "appId": {
47
79
  "type": "string",
@@ -58,10 +90,14 @@
58
90
  "encodingAESKey": {
59
91
  "type": "string",
60
92
  "description": "WeChat MP encoding AES key"
93
+ },
94
+ "id": {
95
+ "type": "string",
96
+ "description": "WeChat MP bot name"
61
97
  }
62
98
  },
63
99
  "required": [
64
- "name",
100
+ "id",
65
101
  "appId",
66
102
  "appSecret",
67
103
  "token"
package/src/endpoint.ts CHANGED
@@ -5,7 +5,7 @@ import axios from 'axios';
5
5
  import type { EndpointFriend, EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
6
6
  import type { MessageGateway } from '@zhin.js/core/runtime';
7
7
  import type { HttpHost, HttpRouteRegistration } from '@zhin.js/host-http';
8
- import { formatCompact, getLogger } from '@zhin.js/logger';
8
+ import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
9
9
  import type { CapabilityId } from '@zhin.js/plugin-runtime';
10
10
  import {
11
11
  extractOutboundText,
@@ -30,8 +30,6 @@ import {
30
30
  } from './passive-reply.js';
31
31
  import { registerWeChatMpWebhookRoutes } from './webhook.js';
32
32
 
33
- const logger = getLogger('wechat-mp');
34
-
35
33
  /** token 失效类错误码:40001/40014 invalid access_token、42001 access_token expired。 */
36
34
  const TOKEN_INVALID_ERRCODES = new Set([40001, 40014, 42001]);
37
35
 
@@ -72,6 +70,8 @@ function defaultFetch(
72
70
  }
73
71
 
74
72
  export class WeChatMpEndpoint implements EndpointInstance {
73
+ readonly #logger!: ReturnType<typeof getAdapterLogger>;
74
+
75
75
  readonly #options: WeChatMpEndpointOptions;
76
76
  readonly #fetch: WeChatMpFetch;
77
77
  #routeReleases: HttpRouteRegistration[] = [];
@@ -86,6 +86,7 @@ export class WeChatMpEndpoint implements EndpointInstance {
86
86
  readonly management: EndpointManagement = createWeChatMpEndpointManagement(this);
87
87
 
88
88
  constructor(options: WeChatMpEndpointOptions) {
89
+ this.#logger = getAdapterLogger('wechat-mp', options.config.id);
89
90
  this.#options = options;
90
91
  this.#fetch = options.fetch ?? defaultFetch;
91
92
  }
@@ -129,14 +130,14 @@ export class WeChatMpEndpoint implements EndpointInstance {
129
130
  await this.#refreshAccessToken();
130
131
  this.#routeReleases.push(...registerWeChatMpWebhookRoutes(this.#options.http, this));
131
132
  this.#startTokenRefreshTimer();
132
- logger.debug(formatCompact({
133
- endpoint: this.#options.config.name,
133
+ this.#logger.debug(formatCompact({
134
+ endpoint: this.#options.config.id,
134
135
  op: 'webhook',
135
136
  path: this.#options.config.path,
136
137
  }));
137
138
  } catch (error) {
138
139
  await this.stop();
139
- logger.error('Failed to connect WeChat MP bot:', error);
140
+ this.#logger.error('Failed to connect WeChat MP bot:', error);
140
141
  throw error;
141
142
  }
142
143
  }
@@ -157,7 +158,7 @@ export class WeChatMpEndpoint implements EndpointInstance {
157
158
  }
158
159
  for (const release of this.#routeReleases.splice(0)) release();
159
160
  this.#started = false;
160
- logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
161
+ this.#logger.debug(formatCompact({ op: 'disconnect' }));
161
162
  }
162
163
 
163
164
  async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
@@ -171,10 +172,10 @@ export class WeChatMpEndpoint implements EndpointInstance {
171
172
  return this.#sendCustomerService(conversation.id, payload);
172
173
  }
173
174
 
174
- logger.warn(formatCompact({
175
+ this.#logger.warn(formatCompact({
175
176
  op: 'send',
176
177
  skip: 'passive_outside_webhook',
177
- endpoint: this.#options.config.name,
178
+ endpoint: this.#options.config.id,
178
179
  target: `${conversation.kind}:${conversation.id}`,
179
180
  }));
180
181
  return `passive_skipped_${Date.now()}`;
@@ -188,15 +189,15 @@ export class WeChatMpEndpoint implements EndpointInstance {
188
189
  conversation,
189
190
  message: { conversation, id: formatInboundId(msg) },
190
191
  content: formatInboundContent(msg),
191
- sender: msg.FromUserName,
192
+ sender: { id: msg.FromUserName },
193
+ endpointId: this.#options.config.id,
192
194
  metadata: Object.freeze({
193
195
  msgType: msg.MsgType,
194
196
  event: msg.Event,
195
- endpoint: this.#options.config.name,
196
197
  toUserName: msg.ToUserName,
197
198
  }),
198
199
  }).catch((err) => {
199
- logger.warn(formatCompact({
200
+ this.#logger.warn(formatCompact({
200
201
  op: 'wechat_mp_gateway_receive_failed',
201
202
  target: `${conversation.kind}:${conversation.id}`,
202
203
  error: err instanceof Error ? err.message : String(err),
@@ -214,7 +215,7 @@ export class WeChatMpEndpoint implements EndpointInstance {
214
215
  let result = await this.#postCustomerService(messageData);
215
216
  if (result.errcode && TOKEN_INVALID_ERRCODES.has(Number(result.errcode))) {
216
217
  // 对端提前作废 token(多端共用等):刷新后重试一次。
217
- logger.warn(formatCompact({
218
+ this.#logger.warn(formatCompact({
218
219
  op: 'wechat_mp_token_invalid_retry',
219
220
  errcode: result.errcode,
220
221
  }));
@@ -224,7 +225,7 @@ export class WeChatMpEndpoint implements EndpointInstance {
224
225
  if (result.errcode && result.errcode !== 0) {
225
226
  throw new Error(`WeChat API error: ${result.errcode} - ${result.errmsg}`);
226
227
  }
227
- logger.debug(formatCompact({ op: 'wechat_mp_send', target, messageId: result.msgid }));
228
+ this.#logger.debug(formatCompact({ op: 'wechat_mp_send', target, messageId: result.msgid }));
228
229
  return result.msgid?.toString() || `cs_${Date.now()}`;
229
230
  }
230
231
 
@@ -250,9 +251,9 @@ export class WeChatMpEndpoint implements EndpointInstance {
250
251
  // 已物化(mediaId/media_id)的段透传;其余无 canonical 媒体引用,丢弃留痕
251
252
  if (typeof data.mediaId === 'string' && data.mediaId) return item;
252
253
  if (typeof data.media_id === 'string' && data.media_id) return item;
253
- logger.warn(formatCompact({
254
+ this.#logger.warn(formatCompact({
254
255
  op: 'wechat_mp_outbound_media_dropped',
255
- endpoint: this.#options.config.name,
256
+ endpoint: this.#options.config.id,
256
257
  type: seg.type,
257
258
  reason: 'missing_media_ref',
258
259
  }));
@@ -263,9 +264,9 @@ export class WeChatMpEndpoint implements EndpointInstance {
263
264
  return { type: seg.type, data: { mediaId: media.value } };
264
265
  }
265
266
  if (!uploadType) {
266
- logger.warn(formatCompact({
267
+ this.#logger.warn(formatCompact({
267
268
  op: 'wechat_mp_outbound_media_dropped',
268
- endpoint: this.#options.config.name,
269
+ endpoint: this.#options.config.id,
269
270
  type: seg.type,
270
271
  reason: 'unsupported_segment_type',
271
272
  }));
@@ -275,9 +276,9 @@ export class WeChatMpEndpoint implements EndpointInstance {
275
276
  const mediaId = await this.#uploadMedia(uploadType, media);
276
277
  return { type: seg.type, data: { mediaId } };
277
278
  } catch (error) {
278
- logger.warn(formatCompact({
279
+ this.#logger.warn(formatCompact({
279
280
  op: 'wechat_mp_media_upload_failed',
280
- endpoint: this.#options.config.name,
281
+ endpoint: this.#options.config.id,
281
282
  error: error instanceof Error ? error.message : String(error),
282
283
  }));
283
284
  const alt = typeof data.alt === 'string' && data.alt ? data.alt : `[${seg.type}]`;
@@ -350,7 +351,7 @@ export class WeChatMpEndpoint implements EndpointInstance {
350
351
  if (data.access_token) {
351
352
  this.#accessToken = data.access_token;
352
353
  this.#tokenExpireTime = Date.now() + (data.expires_in - 300) * 1000;
353
- logger.debug(formatCompact({ op: 'token_refresh' }));
354
+ this.#logger.debug(formatCompact({ op: 'token_refresh' }));
354
355
  return;
355
356
  }
356
357
  throw new Error(
@@ -364,7 +365,7 @@ export class WeChatMpEndpoint implements EndpointInstance {
364
365
  this.#tokenRefreshTimer = setInterval(() => {
365
366
  if (Date.now() >= this.#tokenExpireTime) {
366
367
  void this.#refreshAccessToken().catch((error) => {
367
- logger.error('Failed to refresh access token in timer:', error);
368
+ this.#logger.error('Failed to refresh access token in timer:', error);
368
369
  });
369
370
  }
370
371
  }, 3_600_000);
package/src/protocol.ts CHANGED
@@ -9,7 +9,7 @@ import * as xml2js from 'xml2js';
9
9
  import type { ConversationRef } from '@zhin.js/im-contract';
10
10
 
11
11
  export interface WeChatMpAdapterConfig {
12
- readonly name?: string;
12
+ readonly id?: string;
13
13
  readonly appId?: string;
14
14
  readonly appSecret?: string;
15
15
  readonly token?: string;
@@ -37,7 +37,7 @@ export interface WeChatMpAdapterConfig {
37
37
 
38
38
  export interface ResolvedWeChatMpConfig {
39
39
  readonly context: 'wechat-mp';
40
- readonly name: string;
40
+ readonly id: string;
41
41
  readonly appId: string;
42
42
  readonly appSecret: string;
43
43
  readonly token: string;
@@ -99,8 +99,8 @@ export function resolveWeChatMpConfig(config: WeChatMpAdapterConfig = {}): Resol
99
99
  'WeChat MP adapter requires appId + appSecret + token (plugins.<key> or endpoints with context: wechat-mp)',
100
100
  );
101
101
  }
102
- const name = (typeof config.name === 'string' && config.name)
103
- || (typeof entry?.name === 'string' && entry.name)
102
+ const id = (typeof config.id === 'string' && config.id)
103
+ || (typeof entry?.id === 'string' && entry.id)
104
104
  || process.env.WECHAT_BOT_NAME
105
105
  || 'wechat-mp-bot';
106
106
  const path = config.path ?? entry?.path ?? '/wechat/webhook';
@@ -113,7 +113,7 @@ export function resolveWeChatMpConfig(config: WeChatMpAdapterConfig = {}): Resol
113
113
  ?? 4500;
114
114
  return {
115
115
  context: 'wechat-mp',
116
- name,
116
+ id,
117
117
  appId,
118
118
  appSecret,
119
119
  token,
@@ -316,11 +316,11 @@ export function buildTextReply(
316
316
  * 无群/频道容器(parent 恒缺省)。
317
317
  */
318
318
  export function wechatMpInboundConversation(
319
- endpointId: string,
319
+ endpointKey: string,
320
320
  msg: WeChatMessage,
321
321
  ): ConversationRef {
322
322
  return {
323
- endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
323
+ endpoint: { id: endpointKey, adapter: endpointKey.split('\0')[0] ?? endpointKey },
324
324
  kind: 'private',
325
325
  id: msg.FromUserName,
326
326
  };
package/src/webhook.ts CHANGED
@@ -225,11 +225,11 @@ export async function collectPassiveReply(
225
225
  conversation,
226
226
  message: { conversation, id: formatInboundId(wechatMsg) },
227
227
  content: formatInboundContent(wechatMsg),
228
- sender: wechatMsg.FromUserName,
228
+ sender: { id: wechatMsg.FromUserName },
229
+ endpointId: handler.config.id,
229
230
  metadata: Object.freeze({
230
231
  msgType: wechatMsg.MsgType,
231
232
  event: wechatMsg.Event,
232
- endpoint: handler.config.name,
233
233
  toUserName: wechatMsg.ToUserName,
234
234
  }),
235
235
  }),
File without changes
File without changes
File without changes
File without changes