@zhin.js/adapter-wechat-mp 1.1.18 → 1.1.19
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 +36 -0
- package/README.md +8 -7
- package/adapters/wechat-mp/index.js +3 -17
- package/adapters/wechat-mp/index.ts +2 -17
- package/commands/wechat-mp/endpoint/definition.js +0 -2
- package/commands/wechat-mp/endpoint/definition.ts +0 -2
- package/lib/adapter.d.ts +18 -0
- package/lib/adapter.js +16 -0
- package/lib/api-client.d.ts +34 -0
- package/lib/api-client.js +113 -0
- package/lib/client.d.ts +39 -33
- package/lib/client.js +292 -97
- package/lib/console.d.ts +1 -1
- package/lib/console.js +2 -5
- package/lib/endpoint.d.ts +11 -29
- package/lib/endpoint.js +30 -264
- package/lib/index.d.ts +2 -1
- package/lib/index.js +2 -1
- package/lib/media-upload.d.ts +5 -1
- package/lib/media-upload.js +6 -6
- package/lib/side-event-dispatch.d.ts +1 -1
- package/lib/side-event-dispatch.js +2 -2
- package/lib/webhook.d.ts +2 -2
- package/lib/webhook.js +4 -4
- package/package.json +15 -15
- package/plugin.js +0 -3
- package/src/adapter.ts +22 -0
- package/src/api-client.ts +164 -0
- package/src/client.ts +326 -134
- package/src/console.ts +2 -5
- package/src/endpoint.ts +33 -313
- package/src/index.ts +1 -1
- package/src/media-upload.ts +10 -5
- package/src/side-event-dispatch.ts +2 -1
- package/src/webhook.ts +5 -4
- package/lib/wechat-mp-runtime-state.d.ts +0 -1
- package/lib/wechat-mp-runtime-state.js +0 -6
- package/src/wechat-mp-runtime-state.ts +0 -7
package/lib/client.js
CHANGED
|
@@ -1,105 +1,300 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
1
|
+
import { WeChatMpApiClient } from './api-client.js';
|
|
2
|
+
/**
|
|
3
|
+
* WeChatMpClient — lifecycle, outbound, admit, access token refresh.
|
|
4
|
+
*/
|
|
5
|
+
import axios from 'axios';
|
|
6
|
+
import { createEndpointOperation, } from 'zhin.js/adapter';
|
|
7
|
+
import { formatCompact, getAdapterLogger } from '@zhin.js/logger';
|
|
8
|
+
import { extractOutboundText, formatCustomerServiceBody, formatInboundContent, formatInboundId, wechatMpInboundConversation, } from './protocol.js';
|
|
9
|
+
import { buildMediaUploadForm, readOutboundMedia, resolveMediaBinary, } from './media-upload.js';
|
|
10
|
+
import { getPassiveReplyCapture, recordPassiveReplyText, } from './passive-reply.js';
|
|
11
|
+
import { registerWeChatMpWebhookRoutes } from './webhook.js';
|
|
12
|
+
import { receiveWeChatMpSideEvent } from './side-event-dispatch.js';
|
|
13
|
+
/**
|
|
14
|
+
* canonical 媒体段类型 → 微信 /cgi-bin/media/upload 的 type。
|
|
15
|
+
* 客服消息无 file 投递面,file 段不可投递。
|
|
16
|
+
*/
|
|
17
|
+
const WECHAT_UPLOAD_TYPE = {
|
|
18
|
+
image: 'image',
|
|
19
|
+
audio: 'voice',
|
|
20
|
+
voice: 'voice',
|
|
21
|
+
video: 'video',
|
|
22
|
+
};
|
|
23
|
+
function defaultFetch(url, init) {
|
|
24
|
+
return axios({
|
|
25
|
+
url,
|
|
26
|
+
method: (init?.method ?? 'GET'),
|
|
27
|
+
data: init?.body,
|
|
28
|
+
headers: init?.headers,
|
|
29
|
+
signal: init?.signal,
|
|
30
|
+
}).then((response) => ({ data: response.data }));
|
|
31
|
+
}
|
|
32
|
+
export class WeChatMpClient extends WeChatMpApiClient {
|
|
33
|
+
#logger;
|
|
34
|
+
#options;
|
|
35
|
+
#routeReleases = [];
|
|
36
|
+
#tokenRefreshTimer;
|
|
37
|
+
/** MsgId → 首次回复 XML(微信 5s 重推去重,有界 LRU)。 */
|
|
38
|
+
#replyCache = new Map();
|
|
39
|
+
static #REPLY_CACHE_LIMIT = 1000;
|
|
40
|
+
#open = false;
|
|
41
|
+
#started = false;
|
|
42
|
+
endpointManagement = createWeChatMpEndpointManagement(() => this);
|
|
43
|
+
configName() { return this.#options.config.id; }
|
|
44
|
+
resolvePresentationLocale(_senderId) { return Promise.resolve(undefined); }
|
|
45
|
+
get transportState() { return undefined; }
|
|
46
|
+
executionSignal;
|
|
47
|
+
setExecutionSignal(signal) { this.executionSignal = signal; this.transportSignal = signal; }
|
|
48
|
+
/** Native protocol work runs here; Endpoint attaches the framework receipt without replacing SDK APIs. */
|
|
49
|
+
receiveInboundCallback(receive) {
|
|
50
|
+
try {
|
|
51
|
+
return Promise.resolve(receive());
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
return Promise.reject(error);
|
|
55
|
+
}
|
|
23
56
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
57
|
+
publishEvent(name, payload) { this.emit(name, payload); return Promise.resolve(); }
|
|
58
|
+
publishAccepted(name, payload) { return this.publishEvent(name, payload).then(() => undefined); }
|
|
59
|
+
publishPlatform(name, event) { this.emit(name, event); return Promise.resolve(); }
|
|
60
|
+
constructor(options) {
|
|
61
|
+
super(options.config, options.fetch ?? defaultFetch);
|
|
62
|
+
this.#logger = getAdapterLogger('wechat-mp', options.config.id);
|
|
63
|
+
this.#options = options;
|
|
64
|
+
}
|
|
65
|
+
/** Used by webhook handler. */
|
|
66
|
+
get isOpen() {
|
|
67
|
+
return this.#open;
|
|
68
|
+
}
|
|
69
|
+
get config() {
|
|
70
|
+
return this.#options.config;
|
|
71
|
+
}
|
|
72
|
+
get id() {
|
|
73
|
+
return this.#options.id;
|
|
74
|
+
}
|
|
75
|
+
/** 微信 5s 重推去重:见过该 MsgId 时返回首次回复 XML(含空串=success)。 */
|
|
76
|
+
getCachedReply(msgId) {
|
|
77
|
+
return this.#replyCache.get(msgId);
|
|
78
|
+
}
|
|
79
|
+
cacheReply(msgId, replyXML) {
|
|
80
|
+
if (this.#replyCache.has(msgId))
|
|
81
|
+
this.#replyCache.delete(msgId);
|
|
82
|
+
this.#replyCache.set(msgId, replyXML);
|
|
83
|
+
while (this.#replyCache.size > WeChatMpClient.#REPLY_CACHE_LIMIT) {
|
|
84
|
+
const oldest = this.#replyCache.keys().next().value;
|
|
85
|
+
if (oldest === undefined)
|
|
86
|
+
break;
|
|
87
|
+
this.#replyCache.delete(oldest);
|
|
33
88
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
let timeout;
|
|
40
|
-
const response = await Promise.race([
|
|
41
|
-
this.fetch(url),
|
|
42
|
-
new Promise((_, reject) => {
|
|
43
|
-
timeout = setTimeout(() => reject(new Error('Failed to get access token: request timed out')), TOKEN_FETCH_TIMEOUT_MS);
|
|
44
|
-
}),
|
|
45
|
-
]).finally(() => clearTimeout(timeout));
|
|
46
|
-
const data = response.data;
|
|
47
|
-
if (!data.access_token) {
|
|
48
|
-
throw new Error(data.errmsg
|
|
49
|
-
? `Failed to get access token: ${data.errcode} ${data.errmsg}`
|
|
50
|
-
: 'Failed to get access token');
|
|
89
|
+
}
|
|
90
|
+
async connectTransport(signal) {
|
|
91
|
+
if (signal) {
|
|
92
|
+
this.setExecutionSignal(signal);
|
|
93
|
+
signal.throwIfAborted();
|
|
51
94
|
}
|
|
52
|
-
this.#
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
? Math.min(expiresIn, MAX_TOKEN_LIFETIME_SEC)
|
|
57
|
-
: MAX_TOKEN_LIFETIME_SEC;
|
|
58
|
-
this.#tokenExpireTime = Date.now() + (lifetimeSec - 300) * 1000;
|
|
59
|
-
return data.access_token;
|
|
60
|
-
}
|
|
61
|
-
async request(path, init) {
|
|
62
|
-
return (await this.#requestWithToken(path, init)).data;
|
|
63
|
-
}
|
|
64
|
-
async #requestWithToken(path, init) {
|
|
65
|
-
if (this.tokenExpired)
|
|
95
|
+
if (this.#started)
|
|
96
|
+
return;
|
|
97
|
+
this.#started = true;
|
|
98
|
+
try {
|
|
66
99
|
await this.refreshAccessToken();
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
100
|
+
this.executionSignal?.throwIfAborted();
|
|
101
|
+
const isOpen = () => this.#open;
|
|
102
|
+
this.#routeReleases.push(...registerWeChatMpWebhookRoutes(this.#options.http, {
|
|
103
|
+
config: this.config, get isOpen() { return isOpen(); }, id: this.#options.id,
|
|
104
|
+
admit: (message, event) => this.admit(message, event),
|
|
105
|
+
resolveLocale: senderId => this.resolvePresentationLocale(senderId),
|
|
106
|
+
getCachedReply: id => this.getCachedReply(id), cacheReply: (id, reply) => this.cacheReply(id, reply),
|
|
107
|
+
}));
|
|
108
|
+
this.#startTokenRefreshTimer();
|
|
109
|
+
this.#logger.debug(formatCompact({
|
|
110
|
+
endpoint: this.#options.config.id,
|
|
111
|
+
op: 'webhook',
|
|
112
|
+
path: this.#options.config.path,
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
await this.disconnectTransport();
|
|
117
|
+
this.#logger.error('Failed to connect WeChat MP bot:', error);
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
openInput() {
|
|
122
|
+
this.#open = true;
|
|
123
|
+
}
|
|
124
|
+
closeInput() {
|
|
125
|
+
this.#open = false;
|
|
126
|
+
}
|
|
127
|
+
async disconnectTransport() {
|
|
128
|
+
this.#open = false;
|
|
129
|
+
if (this.#tokenRefreshTimer) {
|
|
130
|
+
clearInterval(this.#tokenRefreshTimer);
|
|
131
|
+
this.#tokenRefreshTimer = undefined;
|
|
132
|
+
}
|
|
133
|
+
for (const release of this.#routeReleases.splice(0))
|
|
134
|
+
release();
|
|
135
|
+
this.#started = false;
|
|
136
|
+
this.#logger.debug(formatCompact({ op: 'disconnect' }));
|
|
137
|
+
}
|
|
138
|
+
async sendPayload({ conversation, payload, signal }) {
|
|
139
|
+
if (getPassiveReplyCapture()) {
|
|
140
|
+
const text = extractOutboundText(payload);
|
|
141
|
+
recordPassiveReplyText(text);
|
|
142
|
+
return `passive_${Date.now()}`;
|
|
80
143
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
144
|
+
if (this.#options.config.replyMode === 'customer_service') {
|
|
145
|
+
return this.#sendCustomerService({ target: conversation.id, payload, signal });
|
|
146
|
+
}
|
|
147
|
+
this.#logger.warn(formatCompact({
|
|
148
|
+
op: 'send',
|
|
149
|
+
skip: 'passive_outside_webhook',
|
|
150
|
+
endpoint: this.#options.config.id,
|
|
151
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
152
|
+
}));
|
|
153
|
+
return `passive_skipped_${Date.now()}`;
|
|
154
|
+
}
|
|
155
|
+
/** Test / internal: admit a parsed message when open (non-webhook path). */
|
|
156
|
+
admit(msg, nativeEvent = msg) {
|
|
157
|
+
if (!this.#open)
|
|
158
|
+
return;
|
|
159
|
+
return this.receiveInboundCallback(() => this.#admitMessage(msg, nativeEvent));
|
|
160
|
+
}
|
|
161
|
+
#admitMessage(msg, nativeEvent = msg) {
|
|
162
|
+
if (!this.#open)
|
|
163
|
+
return;
|
|
164
|
+
void this.publishPlatform(msg.Event ? `${msg.MsgType}.${msg.Event}` : msg.MsgType, nativeEvent).catch((error) => {
|
|
165
|
+
this.#logger.warn(formatCompact({
|
|
166
|
+
op: 'wechat_mp_platform_event_failed',
|
|
167
|
+
error: error instanceof Error ? error.message : String(error),
|
|
168
|
+
}));
|
|
169
|
+
});
|
|
170
|
+
if (receiveWeChatMpSideEvent((name, payload) => this.publishEvent(name, payload), this.#options.config.id, msg, this.#logger, nativeEvent)) {
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
const conversation = wechatMpInboundConversation(String(this.#options.id), msg);
|
|
174
|
+
return this.publishEvent('message.receive', {
|
|
175
|
+
event: nativeEvent,
|
|
176
|
+
conversation,
|
|
177
|
+
message: { conversation, id: formatInboundId(msg) },
|
|
178
|
+
content: formatInboundContent(msg),
|
|
179
|
+
sender: { id: msg.FromUserName },
|
|
180
|
+
endpointId: this.#options.config.id,
|
|
181
|
+
metadata: Object.freeze({
|
|
182
|
+
msgType: msg.MsgType,
|
|
183
|
+
event: msg.Event,
|
|
184
|
+
toUserName: msg.ToUserName,
|
|
185
|
+
}),
|
|
186
|
+
}).catch((err) => {
|
|
187
|
+
this.#logger.warn(formatCompact({
|
|
188
|
+
op: 'wechat_mp_gateway_receive_failed',
|
|
189
|
+
target: `${conversation.kind}:${conversation.id}`,
|
|
190
|
+
error: err instanceof Error ? err.message : String(err),
|
|
191
|
+
}));
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
async #sendCustomerService({ target, payload, signal }) {
|
|
195
|
+
// 发送前检查过期(不只判 null):过期 token 直接刷新,不白跑一次 40001。
|
|
196
|
+
const materialized = await createEndpointOperation({ signal }).prepare(() => this.#materializeOutboundMedia(payload, { signal }));
|
|
197
|
+
const messageData = formatCustomerServiceBody(target, materialized);
|
|
198
|
+
const result = await this.sendCustomerService(messageData, { signal });
|
|
199
|
+
if (result.errcode && result.errcode !== 0) {
|
|
200
|
+
throw new Error(`WeChat API error: ${result.errcode} - ${result.errmsg}`);
|
|
201
|
+
}
|
|
202
|
+
this.#logger.debug(formatCompact({ op: 'wechat_mp_send', target, messageId: result.msgid }));
|
|
203
|
+
return result.msgid?.toString() || `cs_${Date.now()}`;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* 客服消息媒体段只接受 media_id:canonical MediaRef 是唯一来源。
|
|
207
|
+
* - kind=file(平台不透明引用,即既有 media_id)→ 直接透传;
|
|
208
|
+
* - kind=base64 / path / url → 经 /cgi-bin/media/upload 物化;
|
|
209
|
+
* - 无 MediaRef / 类型不可投递(file 段)→ warn + 丢弃;
|
|
210
|
+
* - 上传失败降级为文本(alt 优先),不阻断发送。
|
|
211
|
+
*/
|
|
212
|
+
async #materializeOutboundMedia(payload, request) {
|
|
213
|
+
if (!Array.isArray(payload))
|
|
214
|
+
return payload;
|
|
215
|
+
const materialized = await Promise.all(payload.map(async (item) => {
|
|
216
|
+
if (typeof item === 'string' || !item || typeof item !== 'object')
|
|
217
|
+
return item;
|
|
218
|
+
const seg = item;
|
|
219
|
+
if (typeof seg.type !== 'string')
|
|
220
|
+
return item;
|
|
221
|
+
const data = seg.data ?? {};
|
|
222
|
+
const uploadType = WECHAT_UPLOAD_TYPE[seg.type];
|
|
223
|
+
const isMediaSegment = uploadType != null || seg.type === 'file';
|
|
224
|
+
if (!isMediaSegment)
|
|
225
|
+
return item;
|
|
226
|
+
const media = readOutboundMedia(data);
|
|
227
|
+
if (!media) {
|
|
228
|
+
// 已物化(mediaId/media_id)的段透传;其余无 canonical 媒体引用,丢弃留痕
|
|
229
|
+
if (typeof data.mediaId === 'string' && data.mediaId)
|
|
230
|
+
return item;
|
|
231
|
+
if (typeof data.media_id === 'string' && data.media_id)
|
|
232
|
+
return item;
|
|
233
|
+
this.#logger.warn(formatCompact({
|
|
234
|
+
op: 'wechat_mp_outbound_media_dropped',
|
|
235
|
+
endpoint: this.#options.config.id,
|
|
236
|
+
type: seg.type,
|
|
237
|
+
reason: 'missing_media_ref',
|
|
238
|
+
}));
|
|
239
|
+
return null;
|
|
96
240
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
241
|
+
if (media.kind === 'file') {
|
|
242
|
+
// 平台不透明引用:value 即 media_id,直接透传不上传
|
|
243
|
+
return { type: seg.type, data: { mediaId: media.value } };
|
|
244
|
+
}
|
|
245
|
+
if (!uploadType) {
|
|
246
|
+
this.#logger.warn(formatCompact({
|
|
247
|
+
op: 'wechat_mp_outbound_media_dropped',
|
|
248
|
+
endpoint: this.#options.config.id,
|
|
249
|
+
type: seg.type,
|
|
250
|
+
reason: 'unsupported_segment_type',
|
|
251
|
+
}));
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
const mediaId = await this.#uploadMedia({ type: uploadType, media, signal: request.signal });
|
|
256
|
+
return { type: seg.type, data: { mediaId } };
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
if (request.signal?.aborted)
|
|
260
|
+
throw error;
|
|
261
|
+
this.#logger.warn(formatCompact({
|
|
262
|
+
op: 'wechat_mp_media_upload_failed',
|
|
263
|
+
endpoint: this.#options.config.id,
|
|
264
|
+
error: error instanceof Error ? error.message : String(error),
|
|
265
|
+
}));
|
|
266
|
+
const alt = typeof data.alt === 'string' && data.alt ? data.alt : `[${seg.type}]`;
|
|
267
|
+
return { type: 'text', data: { text: alt } };
|
|
268
|
+
}
|
|
269
|
+
}));
|
|
270
|
+
return materialized.filter((item) => item != null);
|
|
103
271
|
}
|
|
272
|
+
/** POST /cgi-bin/media/upload(临时素材,3 天有效),返回 media_id。 */
|
|
273
|
+
async #uploadMedia({ type, media, signal }) {
|
|
274
|
+
const binary = await createEndpointOperation({ signal }).prepare(() => resolveMediaBinary(media, { signal }));
|
|
275
|
+
const form = buildMediaUploadForm(binary);
|
|
276
|
+
return this.uploadMedia({ type, body: form, signal });
|
|
277
|
+
}
|
|
278
|
+
#startTokenRefreshTimer() {
|
|
279
|
+
if (this.#tokenRefreshTimer)
|
|
280
|
+
clearInterval(this.#tokenRefreshTimer);
|
|
281
|
+
this.#tokenRefreshTimer = setInterval(() => {
|
|
282
|
+
if (this.tokenExpired) {
|
|
283
|
+
void this.refreshAccessToken().catch((error) => {
|
|
284
|
+
this.#logger.error('Failed to refresh access token in timer:', error);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}, 3_600_000);
|
|
288
|
+
this.#tokenRefreshTimer.unref?.();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function createWeChatMpEndpointManagement(requireClient) {
|
|
292
|
+
return Object.freeze({
|
|
293
|
+
// 公众号无群/频道概念;关注者即"好友"(nickname 为 openid 占位,见 getFollowers)。
|
|
294
|
+
listFriends: async (request = {}) => (await requireClient().getFollowerIds(request)).map((openid) => ({
|
|
295
|
+
user_id: openid,
|
|
296
|
+
nickname: openid,
|
|
297
|
+
remark: '',
|
|
298
|
+
})),
|
|
299
|
+
});
|
|
104
300
|
}
|
|
105
|
-
export const wechatMpClient = defineEndpointClient('wechat-mp');
|
package/lib/console.d.ts
CHANGED
|
@@ -5,4 +5,4 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { type AdapterConsoleResources } from 'zhin.js/adapter';
|
|
7
7
|
import type { PluginConsoleRpc } from 'zhin.js';
|
|
8
|
-
export declare function createWechatMpConsoleRpc(
|
|
8
|
+
export declare function createWechatMpConsoleRpc(_resources: AdapterConsoleResources, getConfig: () => unknown): PluginConsoleRpc;
|
package/lib/console.js
CHANGED
|
@@ -4,13 +4,10 @@
|
|
|
4
4
|
* 页面绑定 `pages/index`,读取 / 写入分别要求 `<adapter>:read` / `<adapter>:write`。
|
|
5
5
|
*/
|
|
6
6
|
import { createAdapterConsoleRpc } from 'zhin.js/adapter';
|
|
7
|
-
import { wechatMpRuntimeStateToken } from './wechat-mp-runtime-state.js';
|
|
8
7
|
import { wechatMpConsoleSpec } from './console-spec.js';
|
|
9
|
-
export function createWechatMpConsoleRpc(
|
|
10
|
-
const state = resources.use(wechatMpRuntimeStateToken);
|
|
8
|
+
export function createWechatMpConsoleRpc(_resources, getConfig) {
|
|
11
9
|
return createAdapterConsoleRpc({
|
|
12
10
|
...wechatMpConsoleSpec,
|
|
13
|
-
running: () => state.endpoints.values(),
|
|
14
11
|
getConfig,
|
|
15
|
-
}
|
|
12
|
+
});
|
|
16
13
|
}
|
package/lib/endpoint.d.ts
CHANGED
|
@@ -1,32 +1,14 @@
|
|
|
1
|
-
import { Endpoint } from 'zhin.js/adapter';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
import { WeChatMpClient, type WeChatMpFetch } from './client.js';
|
|
7
|
-
export interface WeChatMpEndpointOptions {
|
|
8
|
-
readonly id: CapabilityId;
|
|
9
|
-
readonly http: HttpHost;
|
|
10
|
-
readonly config: ResolvedWeChatMpConfig;
|
|
11
|
-
readonly fetch?: WeChatMpFetch;
|
|
12
|
-
}
|
|
13
|
-
export declare class WeChatMpEndpoint extends Endpoint<WeChatMpClient> {
|
|
1
|
+
import { endpointSpec, type Endpoint } from 'zhin.js/adapter';
|
|
2
|
+
import { WeChatMpClient, type WeChatMpEndpointOptions } from './client.js';
|
|
3
|
+
export type { WeChatMpEndpointOptions } from './client.js';
|
|
4
|
+
/** Actual protocol Client; framework lifecycle and admission stay in their symbol namespace. */
|
|
5
|
+
export declare class WeChatMpEndpoint extends WeChatMpClient implements Endpoint {
|
|
14
6
|
#private;
|
|
15
|
-
readonly
|
|
16
|
-
readonly management: EndpointManagement;
|
|
7
|
+
readonly [endpointSpec]: import("@zhin.js/adapter").EndpointSpecification;
|
|
17
8
|
constructor(options: WeChatMpEndpointOptions);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
getCachedReply(msgId: string): string | undefined;
|
|
24
|
-
cacheReply(msgId: string, replyXML: string): void;
|
|
25
|
-
start(): Promise<void>;
|
|
26
|
-
open(): void;
|
|
27
|
-
close(): void;
|
|
28
|
-
stop(): Promise<void>;
|
|
29
|
-
send({ conversation, payload }: EndpointSendRequest): Promise<string>;
|
|
30
|
-
/** Test / internal: admit a parsed message when open (non-webhook path). */
|
|
31
|
-
admit(msg: WeChatMessage): void | Promise<unknown>;
|
|
9
|
+
protected resolvePresentationLocale(senderId: string): Promise<import("zhin.js").Locale | undefined>;
|
|
10
|
+
protected receiveInboundCallback<T>(receive: () => T | Promise<T>): Promise<T>;
|
|
11
|
+
protected publishEvent(name: string, payload: unknown): Promise<unknown>;
|
|
12
|
+
protected publishAccepted(name: string, payload: unknown): Promise<void>;
|
|
13
|
+
protected publishPlatform(name: string, event: unknown): Promise<unknown>;
|
|
32
14
|
}
|