@zhin.js/adapter-wechat-mp 5.0.0 → 5.0.2
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 +27 -0
- package/README.md +2 -2
- package/lib/endpoint.d.ts +2 -5
- package/lib/endpoint.js +8 -8
- package/lib/protocol.d.ts +6 -0
- package/lib/protocol.js +11 -0
- package/lib/webhook.js +4 -4
- package/package.json +12 -11
- package/src/endpoint.ts +9 -8
- package/src/protocol.ts +16 -0
- package/src/webhook.ts +4 -3
- /package/commands/endpoint/add/{[name:string].js → [name].js} +0 -0
- /package/commands/endpoint/add/{[name:string].ts → [name].ts} +0 -0
- /package/commands/endpoint/remove/{[name:string].js → [name].js} +0 -0
- /package/commands/endpoint/remove/{[name:string].ts → [name].ts} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# @zhin.js/adapter-wechat-mp
|
|
2
2
|
|
|
3
|
+
## 5.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [afc0e66]
|
|
8
|
+
- Updated dependencies [2e41ad5]
|
|
9
|
+
- Updated dependencies [9f57124]
|
|
10
|
+
- @zhin.js/core@1.5.2
|
|
11
|
+
- @zhin.js/adapter@1.1.5
|
|
12
|
+
- @zhin.js/im-contract@1.0.1
|
|
13
|
+
- @zhin.js/plugin-runtime@1.1.3
|
|
14
|
+
- @zhin.js/command@1.0.7
|
|
15
|
+
- @zhin.js/host-http@1.0.6
|
|
16
|
+
- zhin.js@6.0.2
|
|
17
|
+
|
|
18
|
+
## 5.0.1
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Updated dependencies [c8f4d45]
|
|
23
|
+
- @zhin.js/plugin-runtime@1.1.2
|
|
24
|
+
- @zhin.js/host-http@1.0.5
|
|
25
|
+
- @zhin.js/adapter@1.1.4
|
|
26
|
+
- @zhin.js/command@1.0.6
|
|
27
|
+
- @zhin.js/core@1.5.1
|
|
28
|
+
- zhin.js@6.0.1
|
|
29
|
+
|
|
3
30
|
## 5.0.0
|
|
4
31
|
|
|
5
32
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -24,8 +24,8 @@ pnpm add @zhin.js/adapter-wechat-mp
|
|
|
24
24
|
- `@zhin.js/plugin-runtime` — `plugin.ts`(`definePlugin`)
|
|
25
25
|
- 配置经插件 `schema.json` 落到 `plugins.<instanceKey>`
|
|
26
26
|
|
|
27
|
-
入站:`gateway.receive({
|
|
28
|
-
出站:`send({
|
|
27
|
+
入站:`gateway.receive({ conversation: { endpoint, kind: 'private', id: openid }, message: { conversation, id }, content: text, sender, metadata })`
|
|
28
|
+
出站:`send({ conversation, payload })` → 被动回复 XML(默认)或客服消息 API(`replyMode: customer_service`)
|
|
29
29
|
|
|
30
30
|
## 前置条件
|
|
31
31
|
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EndpointFriend, EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
|
|
1
|
+
import type { EndpointFriend, EndpointInstance, EndpointManagement, EndpointSendRequest } from '@zhin.js/adapter';
|
|
2
2
|
import type { MessageGateway } from '@zhin.js/core/runtime';
|
|
3
3
|
import type { HttpHost } from '@zhin.js/host-http';
|
|
4
4
|
import type { CapabilityId } from '@zhin.js/plugin-runtime';
|
|
@@ -33,10 +33,7 @@ export declare class WeChatMpEndpoint implements EndpointInstance {
|
|
|
33
33
|
open(): void;
|
|
34
34
|
close(): void;
|
|
35
35
|
stop(): Promise<void>;
|
|
36
|
-
send({
|
|
37
|
-
readonly target: string;
|
|
38
|
-
readonly payload: unknown;
|
|
39
|
-
}): Promise<string>;
|
|
36
|
+
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
40
37
|
/** Test / internal: admit a parsed message when open (non-webhook path). */
|
|
41
38
|
admit(msg: WeChatMessage): void;
|
|
42
39
|
/**
|
package/lib/endpoint.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import axios from 'axios';
|
|
5
5
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
6
|
-
import { extractOutboundText, formatCustomerServiceBody, formatInboundContent, formatInboundId, } from './protocol.js';
|
|
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';
|
|
@@ -110,20 +110,20 @@ export class WeChatMpEndpoint {
|
|
|
110
110
|
this.#started = false;
|
|
111
111
|
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
|
|
112
112
|
}
|
|
113
|
-
async send({
|
|
113
|
+
async send({ conversation, payload }) {
|
|
114
114
|
if (getPassiveReplyCapture()) {
|
|
115
115
|
const text = extractOutboundText(payload);
|
|
116
116
|
recordPassiveReplyText(text);
|
|
117
117
|
return `passive_${Date.now()}`;
|
|
118
118
|
}
|
|
119
119
|
if (this.#options.config.replyMode === 'customer_service') {
|
|
120
|
-
return this.#sendCustomerService(
|
|
120
|
+
return this.#sendCustomerService(conversation.id, payload);
|
|
121
121
|
}
|
|
122
122
|
logger.warn(formatCompact({
|
|
123
123
|
op: 'send',
|
|
124
124
|
skip: 'passive_outside_webhook',
|
|
125
125
|
endpoint: this.#options.config.name,
|
|
126
|
-
target
|
|
126
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
127
127
|
}));
|
|
128
128
|
return `passive_skipped_${Date.now()}`;
|
|
129
129
|
}
|
|
@@ -131,12 +131,12 @@ export class WeChatMpEndpoint {
|
|
|
131
131
|
admit(msg) {
|
|
132
132
|
if (!this.#open)
|
|
133
133
|
return;
|
|
134
|
+
const conversation = wechatMpInboundConversation(String(this.#options.id), msg);
|
|
134
135
|
void this.#options.gateway.receive({
|
|
135
|
-
|
|
136
|
-
|
|
136
|
+
conversation,
|
|
137
|
+
message: { conversation, id: formatInboundId(msg) },
|
|
137
138
|
content: formatInboundContent(msg),
|
|
138
139
|
sender: msg.FromUserName,
|
|
139
|
-
id: formatInboundId(msg),
|
|
140
140
|
metadata: Object.freeze({
|
|
141
141
|
msgType: msg.MsgType,
|
|
142
142
|
event: msg.Event,
|
|
@@ -146,7 +146,7 @@ export class WeChatMpEndpoint {
|
|
|
146
146
|
}).catch((err) => {
|
|
147
147
|
logger.warn(formatCompact({
|
|
148
148
|
op: 'wechat_mp_gateway_receive_failed',
|
|
149
|
-
target:
|
|
149
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
150
150
|
error: err instanceof Error ? err.message : String(err),
|
|
151
151
|
}));
|
|
152
152
|
});
|
package/lib/protocol.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Canonicalization is owned by gateway/core before endpoint.send.
|
|
4
4
|
*/
|
|
5
5
|
import type { IncomingMessage } from 'node:http';
|
|
6
|
+
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
6
7
|
export interface WeChatMpAdapterConfig {
|
|
7
8
|
readonly name?: string;
|
|
8
9
|
readonly appId?: string;
|
|
@@ -101,6 +102,11 @@ export declare function parseXMLMessage(xmlString: string): Promise<WeChatMessag
|
|
|
101
102
|
export declare function decryptMessage(encryptedXml: string, msgSignature: string, timestamp: string, nonce: string, token: string, encodingAESKey: string, appId: string): Promise<string>;
|
|
102
103
|
export declare function encryptMessage(replyXml: string, token: string, encodingAESKey: string, appId: string, requestTimestamp?: string): string;
|
|
103
104
|
export declare function buildTextReply(wechatMsg: Pick<WeChatMessage, 'FromUserName' | 'ToUserName'>, content: string): string;
|
|
105
|
+
/**
|
|
106
|
+
* 入站归一化:公众号只有粉丝单聊场景,一律 kind='private',id=openid(FromUserName),
|
|
107
|
+
* 无群/频道容器(parent 恒缺省)。
|
|
108
|
+
*/
|
|
109
|
+
export declare function wechatMpInboundConversation(endpointId: string, msg: WeChatMessage): ConversationRef;
|
|
104
110
|
/**
|
|
105
111
|
* 入站消息 id:普通消息用 MsgId;无 MsgId 的事件消息拼 Event/EventKey,
|
|
106
112
|
* 避免只用秒级 CreateTime 时同秒多事件 id 碰撞。
|
package/lib/protocol.js
CHANGED
|
@@ -170,6 +170,17 @@ export function buildTextReply(wechatMsg, content) {
|
|
|
170
170
|
'</xml>',
|
|
171
171
|
].join('');
|
|
172
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* 入站归一化:公众号只有粉丝单聊场景,一律 kind='private',id=openid(FromUserName),
|
|
175
|
+
* 无群/频道容器(parent 恒缺省)。
|
|
176
|
+
*/
|
|
177
|
+
export function wechatMpInboundConversation(endpointId, msg) {
|
|
178
|
+
return {
|
|
179
|
+
endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
|
|
180
|
+
kind: 'private',
|
|
181
|
+
id: msg.FromUserName,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
173
184
|
/**
|
|
174
185
|
* 入站消息 id:普通消息用 MsgId;无 MsgId 的事件消息拼 Event/EventKey,
|
|
175
186
|
* 避免只用秒级 CreateTime 时同秒多事件 id 碰撞。
|
package/lib/webhook.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
2
2
|
import { getPassiveReplyCapture, runWithPassiveReplyCapture, } from './passive-reply.js';
|
|
3
|
-
import { buildTextReply, computeSignatureHash, decryptEchostr, decryptMessage, encryptMessage, formatInboundContent, formatInboundId, isEncryptedEchostr, normalizeEchostrParam, parseXMLMessage, queryParam, readTextBody, resolveEventPassiveReply, verifySignature, } from './protocol.js';
|
|
3
|
+
import { buildTextReply, computeSignatureHash, decryptEchostr, decryptMessage, encryptMessage, formatInboundContent, formatInboundId, isEncryptedEchostr, normalizeEchostrParam, parseXMLMessage, queryParam, readTextBody, resolveEventPassiveReply, verifySignature, wechatMpInboundConversation, } from './protocol.js';
|
|
4
4
|
const logger = getLogger('wechat-mp');
|
|
5
5
|
export function registerWeChatMpWebhookRoutes(http, handler) {
|
|
6
6
|
const path = handler.config.path;
|
|
@@ -132,13 +132,13 @@ export async function handleWeChatMpMessage(request, response, url, handler) {
|
|
|
132
132
|
export async function collectPassiveReply(handler, wechatMsg) {
|
|
133
133
|
const timeoutMs = handler.config.passiveReplyTimeoutMs;
|
|
134
134
|
const text = await runWithPassiveReplyCapture(async () => {
|
|
135
|
+
const conversation = wechatMpInboundConversation(String(handler.id), wechatMsg);
|
|
135
136
|
await Promise.race([
|
|
136
137
|
handler.gateway.receive({
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
conversation,
|
|
139
|
+
message: { conversation, id: formatInboundId(wechatMsg) },
|
|
139
140
|
content: formatInboundContent(wechatMsg),
|
|
140
141
|
sender: wechatMsg.FromUserName,
|
|
141
|
-
id: formatInboundId(wechatMsg),
|
|
142
142
|
metadata: Object.freeze({
|
|
143
143
|
msgType: wechatMsg.MsgType,
|
|
144
144
|
event: wechatMsg.Event,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhin.js/adapter-wechat-mp",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.2",
|
|
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,19 +8,20 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"axios": "^1.19.0",
|
|
10
10
|
"xml2js": "^0.6.2",
|
|
11
|
-
"@zhin.js/adapter": "1.1.
|
|
12
|
-
"@zhin.js/command": "1.0.
|
|
13
|
-
"@zhin.js/core": "1.5.
|
|
14
|
-
"@zhin.js/host-http": "1.0.
|
|
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",
|
|
15
16
|
"@zhin.js/logger": "1.0.75",
|
|
16
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
17
|
+
"@zhin.js/plugin-runtime": "1.1.3"
|
|
17
18
|
},
|
|
18
19
|
"peerDependencies": {
|
|
19
|
-
"@zhin.js/adapter": "1.1.
|
|
20
|
-
"@zhin.js/core": "1.5.
|
|
21
|
-
"@zhin.js/host-http": "1.0.
|
|
22
|
-
"@zhin.js/plugin-runtime": "1.1.
|
|
23
|
-
"zhin.js": "6.0.
|
|
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"
|
|
24
25
|
},
|
|
25
26
|
"peerDependenciesMeta": {
|
|
26
27
|
"zhin.js": {
|
package/src/endpoint.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* WeChatMpEndpoint — lifecycle, outbound, admit, access token refresh.
|
|
3
3
|
*/
|
|
4
4
|
import axios from 'axios';
|
|
5
|
-
import type { EndpointFriend, EndpointInstance, EndpointManagement } from '@zhin.js/adapter';
|
|
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
8
|
import { formatCompact, getLogger } from '@zhin.js/logger';
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
formatCustomerServiceBody,
|
|
13
13
|
formatInboundContent,
|
|
14
14
|
formatInboundId,
|
|
15
|
+
wechatMpInboundConversation,
|
|
15
16
|
type ResolvedWeChatMpConfig,
|
|
16
17
|
type TokenResponse,
|
|
17
18
|
type WeChatAPIResponse,
|
|
@@ -159,7 +160,7 @@ export class WeChatMpEndpoint implements EndpointInstance {
|
|
|
159
160
|
logger.debug(formatCompact({ op: 'disconnect', endpoint: this.#options.config.name }));
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
async send({
|
|
163
|
+
async send({ conversation, payload }: EndpointSendRequest): Promise<string> {
|
|
163
164
|
if (getPassiveReplyCapture()) {
|
|
164
165
|
const text = extractOutboundText(payload);
|
|
165
166
|
recordPassiveReplyText(text);
|
|
@@ -167,14 +168,14 @@ export class WeChatMpEndpoint implements EndpointInstance {
|
|
|
167
168
|
}
|
|
168
169
|
|
|
169
170
|
if (this.#options.config.replyMode === 'customer_service') {
|
|
170
|
-
return this.#sendCustomerService(
|
|
171
|
+
return this.#sendCustomerService(conversation.id, payload);
|
|
171
172
|
}
|
|
172
173
|
|
|
173
174
|
logger.warn(formatCompact({
|
|
174
175
|
op: 'send',
|
|
175
176
|
skip: 'passive_outside_webhook',
|
|
176
177
|
endpoint: this.#options.config.name,
|
|
177
|
-
target
|
|
178
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
178
179
|
}));
|
|
179
180
|
return `passive_skipped_${Date.now()}`;
|
|
180
181
|
}
|
|
@@ -182,12 +183,12 @@ export class WeChatMpEndpoint implements EndpointInstance {
|
|
|
182
183
|
/** Test / internal: admit a parsed message when open (non-webhook path). */
|
|
183
184
|
admit(msg: WeChatMessage): void {
|
|
184
185
|
if (!this.#open) return;
|
|
186
|
+
const conversation = wechatMpInboundConversation(String(this.#options.id), msg);
|
|
185
187
|
void this.#options.gateway.receive({
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
conversation,
|
|
189
|
+
message: { conversation, id: formatInboundId(msg) },
|
|
188
190
|
content: formatInboundContent(msg),
|
|
189
191
|
sender: msg.FromUserName,
|
|
190
|
-
id: formatInboundId(msg),
|
|
191
192
|
metadata: Object.freeze({
|
|
192
193
|
msgType: msg.MsgType,
|
|
193
194
|
event: msg.Event,
|
|
@@ -197,7 +198,7 @@ export class WeChatMpEndpoint implements EndpointInstance {
|
|
|
197
198
|
}).catch((err) => {
|
|
198
199
|
logger.warn(formatCompact({
|
|
199
200
|
op: 'wechat_mp_gateway_receive_failed',
|
|
200
|
-
target:
|
|
201
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
201
202
|
error: err instanceof Error ? err.message : String(err),
|
|
202
203
|
}));
|
|
203
204
|
});
|
package/src/protocol.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { createHash, createDecipheriv, createCipheriv, randomBytes } from 'node:crypto';
|
|
7
7
|
import type { IncomingMessage } from 'node:http';
|
|
8
8
|
import * as xml2js from 'xml2js';
|
|
9
|
+
import type { ConversationRef } from '@zhin.js/im-contract';
|
|
9
10
|
|
|
10
11
|
export interface WeChatMpAdapterConfig {
|
|
11
12
|
readonly name?: string;
|
|
@@ -310,6 +311,21 @@ export function buildTextReply(
|
|
|
310
311
|
].join('');
|
|
311
312
|
}
|
|
312
313
|
|
|
314
|
+
/**
|
|
315
|
+
* 入站归一化:公众号只有粉丝单聊场景,一律 kind='private',id=openid(FromUserName),
|
|
316
|
+
* 无群/频道容器(parent 恒缺省)。
|
|
317
|
+
*/
|
|
318
|
+
export function wechatMpInboundConversation(
|
|
319
|
+
endpointId: string,
|
|
320
|
+
msg: WeChatMessage,
|
|
321
|
+
): ConversationRef {
|
|
322
|
+
return {
|
|
323
|
+
endpoint: { id: endpointId, adapter: endpointId.split('\0')[0] ?? endpointId },
|
|
324
|
+
kind: 'private',
|
|
325
|
+
id: msg.FromUserName,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
313
329
|
/**
|
|
314
330
|
* 入站消息 id:普通消息用 MsgId;无 MsgId 的事件消息拼 Event/EventKey,
|
|
315
331
|
* 避免只用秒级 CreateTime 时同秒多事件 id 碰撞。
|
package/src/webhook.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
readTextBody,
|
|
26
26
|
resolveEventPassiveReply,
|
|
27
27
|
verifySignature,
|
|
28
|
+
wechatMpInboundConversation,
|
|
28
29
|
type ResolvedWeChatMpConfig,
|
|
29
30
|
type WeChatMessage,
|
|
30
31
|
} from './protocol.js';
|
|
@@ -218,13 +219,13 @@ export async function collectPassiveReply(
|
|
|
218
219
|
): Promise<string> {
|
|
219
220
|
const timeoutMs = handler.config.passiveReplyTimeoutMs;
|
|
220
221
|
const text = await runWithPassiveReplyCapture(async () => {
|
|
222
|
+
const conversation = wechatMpInboundConversation(String(handler.id), wechatMsg);
|
|
221
223
|
await Promise.race([
|
|
222
224
|
handler.gateway.receive({
|
|
223
|
-
|
|
224
|
-
|
|
225
|
+
conversation,
|
|
226
|
+
message: { conversation, id: formatInboundId(wechatMsg) },
|
|
225
227
|
content: formatInboundContent(wechatMsg),
|
|
226
228
|
sender: wechatMsg.FromUserName,
|
|
227
|
-
id: formatInboundId(wechatMsg),
|
|
228
229
|
metadata: Object.freeze({
|
|
229
230
|
msgType: wechatMsg.MsgType,
|
|
230
231
|
event: wechatMsg.Event,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|