@zhin.js/adapter-weixin-ilink 1.0.1 → 1.1.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 +512 -0
- package/README.md +24 -42
- package/adapters/weixin-ilink/index.js +32 -0
- package/adapters/weixin-ilink/index.ts +43 -0
- package/commands/weixin-ilink/endpoint/add/[id]/index.js +3 -0
- package/commands/weixin-ilink/endpoint/add/[id]/index.ts +3 -0
- package/commands/weixin-ilink/endpoint/definition.js +17 -0
- package/commands/weixin-ilink/endpoint/definition.ts +17 -0
- package/commands/weixin-ilink/endpoint/list/index.js +3 -0
- package/commands/weixin-ilink/endpoint/list/index.ts +3 -0
- package/commands/weixin-ilink/endpoint/remove/[id]/index.js +3 -0
- package/commands/weixin-ilink/endpoint/remove/[id]/index.ts +3 -0
- package/lib/aes-ecb.d.ts +0 -1
- package/lib/aes-ecb.js +0 -1
- package/lib/cdn-upload.d.ts +0 -1
- package/lib/cdn-upload.js +0 -1
- package/lib/cdn-url.d.ts +0 -1
- package/lib/cdn-url.js +0 -1
- package/lib/client.d.ts +35 -0
- package/lib/client.js +89 -0
- package/lib/context-store.d.ts +15 -2
- package/lib/context-store.js +93 -2
- package/lib/credentials.d.ts +15 -8
- package/lib/credentials.js +59 -43
- package/lib/endpoint.d.ts +40 -35
- package/lib/endpoint.js +311 -262
- package/lib/ilink-api.d.ts +7 -6
- package/lib/ilink-api.js +26 -16
- package/lib/ilink-config-cache.d.ts +3 -8
- package/lib/ilink-config-cache.js +13 -15
- package/lib/ilink-logger.d.ts +0 -2
- package/lib/ilink-logger.js +11 -26
- package/lib/ilink-meta.d.ts +13 -8
- package/lib/ilink-meta.js +24 -21
- package/lib/ilink-random.d.ts +0 -1
- package/lib/ilink-random.js +0 -1
- package/lib/ilink-redact.d.ts +0 -1
- package/lib/ilink-redact.js +0 -1
- package/lib/ilink-session-guard.d.ts +13 -15
- package/lib/ilink-session-guard.js +29 -43
- package/lib/ilink-types.d.ts +0 -1
- package/lib/ilink-types.js +0 -1
- package/lib/index.d.ts +8 -20
- package/lib/index.js +7 -38
- package/lib/login.d.ts +5 -6
- package/lib/login.js +85 -77
- package/lib/media-download.d.ts +1 -2
- package/lib/media-download.js +1 -2
- package/lib/mime.d.ts +0 -1
- package/lib/mime.js +0 -1
- package/lib/outbound-media.d.ts +4 -5
- package/lib/outbound-media.js +85 -86
- package/lib/pic-decrypt.d.ts +0 -1
- package/lib/pic-decrypt.js +0 -1
- package/lib/protocol.d.ts +60 -0
- package/lib/protocol.js +127 -0
- package/lib/silk-transcode.d.ts +0 -1
- package/lib/silk-transcode.js +0 -1
- package/lib/types.d.ts +2 -26
- package/lib/types.js +0 -1
- package/lib/upload.d.ts +1 -2
- package/lib/upload.js +1 -2
- package/lib/weixin-ilink-runtime-state.d.ts +1 -0
- package/lib/weixin-ilink-runtime-state.js +6 -0
- package/lib/weixin-inbound.d.ts +4 -43
- package/lib/weixin-inbound.js +22 -162
- package/lib/weixin-send-media.d.ts +0 -1
- package/lib/weixin-send-media.js +5 -2
- package/lib/weixin-send.d.ts +1 -2
- package/lib/weixin-send.js +4 -3
- package/package.json +48 -39
- package/plugin.js +14 -0
- package/schema.json +116 -0
- package/src/client.ts +117 -0
- package/src/context-store.ts +101 -8
- package/src/credentials.ts +68 -45
- package/src/endpoint.ts +384 -315
- package/src/ilink-api.ts +31 -21
- package/src/ilink-config-cache.ts +17 -13
- package/src/ilink-logger.ts +11 -26
- package/src/ilink-meta.ts +31 -21
- package/src/ilink-session-guard.ts +29 -46
- package/src/index.ts +41 -56
- package/src/login.ts +112 -85
- package/src/media-download.ts +1 -2
- package/src/outbound-media.ts +97 -97
- package/src/protocol.ts +180 -0
- package/src/silk-wasm.d.ts +6 -2
- package/src/types.ts +5 -25
- package/src/upload.test.ts +11 -8
- package/src/upload.ts +2 -2
- package/src/weixin-ilink-runtime-state.ts +7 -0
- package/src/weixin-inbound.ts +25 -187
- package/src/weixin-send-media.ts +5 -1
- package/src/weixin-send.ts +6 -4
- package/client/LoginAssistPanel.tsx +0 -200
- package/client/WeixinIlinkManagement.tsx +0 -139
- package/client/index.tsx +0 -11
- package/client/tsconfig.json +0 -13
- package/client/utils/api.ts +0 -30
- package/dist/index.js +0 -29
- package/lib/adapter.d.ts +0 -12
- package/lib/adapter.d.ts.map +0 -1
- package/lib/adapter.js +0 -15
- package/lib/adapter.js.map +0 -1
- package/lib/aes-ecb.d.ts.map +0 -1
- package/lib/aes-ecb.js.map +0 -1
- package/lib/cdn-upload.d.ts.map +0 -1
- package/lib/cdn-upload.js.map +0 -1
- package/lib/cdn-upload.test.d.ts +0 -2
- package/lib/cdn-upload.test.d.ts.map +0 -1
- package/lib/cdn-upload.test.js +0 -150
- package/lib/cdn-upload.test.js.map +0 -1
- package/lib/cdn-url.d.ts.map +0 -1
- package/lib/cdn-url.js.map +0 -1
- package/lib/context-store.d.ts.map +0 -1
- package/lib/context-store.js.map +0 -1
- package/lib/credentials.d.ts.map +0 -1
- package/lib/credentials.js.map +0 -1
- package/lib/endpoint.d.ts.map +0 -1
- package/lib/endpoint.js.map +0 -1
- package/lib/ilink-api.d.ts.map +0 -1
- package/lib/ilink-api.js.map +0 -1
- package/lib/ilink-config-cache.d.ts.map +0 -1
- package/lib/ilink-config-cache.js.map +0 -1
- package/lib/ilink-logger.d.ts.map +0 -1
- package/lib/ilink-logger.js.map +0 -1
- package/lib/ilink-meta.d.ts.map +0 -1
- package/lib/ilink-meta.js.map +0 -1
- package/lib/ilink-random.d.ts.map +0 -1
- package/lib/ilink-random.js.map +0 -1
- package/lib/ilink-redact.d.ts.map +0 -1
- package/lib/ilink-redact.js.map +0 -1
- package/lib/ilink-session-guard.d.ts.map +0 -1
- package/lib/ilink-session-guard.js.map +0 -1
- package/lib/ilink-types.d.ts.map +0 -1
- package/lib/ilink-types.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
- package/lib/login-assist-routes.d.ts +0 -7
- package/lib/login-assist-routes.d.ts.map +0 -1
- package/lib/login-assist-routes.js +0 -29
- package/lib/login-assist-routes.js.map +0 -1
- package/lib/login.d.ts.map +0 -1
- package/lib/login.js.map +0 -1
- package/lib/media-download.d.ts.map +0 -1
- package/lib/media-download.js.map +0 -1
- package/lib/mime.d.ts.map +0 -1
- package/lib/mime.js.map +0 -1
- package/lib/mime.test.d.ts +0 -2
- package/lib/mime.test.d.ts.map +0 -1
- package/lib/mime.test.js +0 -62
- package/lib/mime.test.js.map +0 -1
- package/lib/outbound-media.d.ts.map +0 -1
- package/lib/outbound-media.js.map +0 -1
- package/lib/pic-decrypt.d.ts.map +0 -1
- package/lib/pic-decrypt.js.map +0 -1
- package/lib/routes.d.ts +0 -8
- package/lib/routes.d.ts.map +0 -1
- package/lib/routes.js +0 -29
- package/lib/routes.js.map +0 -1
- package/lib/silk-transcode.d.ts.map +0 -1
- package/lib/silk-transcode.js.map +0 -1
- package/lib/silk-transcode.test.d.ts +0 -2
- package/lib/silk-transcode.test.d.ts.map +0 -1
- package/lib/silk-transcode.test.js +0 -53
- package/lib/silk-transcode.test.js.map +0 -1
- package/lib/types.d.ts.map +0 -1
- package/lib/types.js.map +0 -1
- package/lib/typing-indicator.d.ts +0 -32
- package/lib/typing-indicator.d.ts.map +0 -1
- package/lib/typing-indicator.js +0 -95
- package/lib/typing-indicator.js.map +0 -1
- package/lib/upload.d.ts.map +0 -1
- package/lib/upload.js.map +0 -1
- package/lib/upload.test.d.ts +0 -2
- package/lib/upload.test.d.ts.map +0 -1
- package/lib/upload.test.js +0 -250
- package/lib/upload.test.js.map +0 -1
- package/lib/weixin-inbound.d.ts.map +0 -1
- package/lib/weixin-inbound.js.map +0 -1
- package/lib/weixin-send-media.d.ts.map +0 -1
- package/lib/weixin-send-media.js.map +0 -1
- package/lib/weixin-send.d.ts.map +0 -1
- package/lib/weixin-send.js.map +0 -1
- package/src/adapter.ts +0 -18
- package/src/login-assist-routes.ts +0 -36
- package/src/routes.ts +0 -39
- package/src/typing-indicator.ts +0 -139
package/lib/ilink-api.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { type IlinkClientMetadata } from "./ilink-meta.js";
|
|
1
2
|
import type { GetUploadUrlReq, GetUploadUrlResp, GetUpdatesReq, GetUpdatesResp, NotifyStopResp, NotifyStartResp, SendMessageReq, SendTypingReq, GetConfigResp } from "./ilink-types.js";
|
|
2
|
-
export {
|
|
3
|
+
export { IlinkClientMetadata, sanitizeBotAgent } from "./ilink-meta.js";
|
|
3
4
|
export type WeixinApiOptions = {
|
|
4
5
|
baseUrl: string;
|
|
6
|
+
metadata: IlinkClientMetadata;
|
|
5
7
|
token?: string;
|
|
6
8
|
timeoutMs?: number;
|
|
7
9
|
/** Long-poll timeout for getUpdates (server may hold the request up to this). */
|
|
@@ -15,9 +17,11 @@ export type WeixinApiOptions = {
|
|
|
15
17
|
*/
|
|
16
18
|
export declare function apiGetFetch(params: {
|
|
17
19
|
baseUrl: string;
|
|
20
|
+
metadata: IlinkClientMetadata;
|
|
18
21
|
endpoint: string;
|
|
19
22
|
timeoutMs?: number;
|
|
20
23
|
label: string;
|
|
24
|
+
abortSignal?: AbortSignal;
|
|
21
25
|
}): Promise<string>;
|
|
22
26
|
/**
|
|
23
27
|
* Common fetch wrapper: POST JSON to a Weixin API endpoint.
|
|
@@ -28,6 +32,7 @@ export declare function apiGetFetch(params: {
|
|
|
28
32
|
*/
|
|
29
33
|
export declare function apiPostFetch(params: {
|
|
30
34
|
baseUrl: string;
|
|
35
|
+
metadata: IlinkClientMetadata;
|
|
31
36
|
endpoint: string;
|
|
32
37
|
body: string;
|
|
33
38
|
token?: string;
|
|
@@ -41,10 +46,7 @@ export declare function apiPostFetch(params: {
|
|
|
41
46
|
* On client-side timeout (no server response within timeoutMs), returns an empty response
|
|
42
47
|
* with ret=0 so the caller can simply retry. This is normal for long-poll.
|
|
43
48
|
*/
|
|
44
|
-
export declare function getUpdates(params: GetUpdatesReq & {
|
|
45
|
-
baseUrl: string;
|
|
46
|
-
token?: string;
|
|
47
|
-
timeoutMs?: number;
|
|
49
|
+
export declare function getUpdates(params: GetUpdatesReq & WeixinApiOptions & {
|
|
48
50
|
/**
|
|
49
51
|
* Optional external abort signal (e.g. from the gateway when stopping the
|
|
50
52
|
* channel). When this aborts, the in-flight long-poll is terminated
|
|
@@ -78,4 +80,3 @@ export declare function notifyStop(params: WeixinApiOptions): Promise<NotifyStop
|
|
|
78
80
|
* Notify Weixin that this channel client is starting (gateway startup / channel start).
|
|
79
81
|
*/
|
|
80
82
|
export declare function notifyStart(params: WeixinApiOptions): Promise<NotifyStartResp>;
|
|
81
|
-
//# sourceMappingURL=ilink-api.d.ts.map
|
package/lib/ilink-api.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import { logger } from "./ilink-logger.js";
|
|
3
|
-
import {
|
|
3
|
+
import { ILINK_APP_ID, } from "./ilink-meta.js";
|
|
4
4
|
import { redactBody, redactUrl } from "./ilink-redact.js";
|
|
5
|
-
export {
|
|
5
|
+
export { IlinkClientMetadata, sanitizeBotAgent } from "./ilink-meta.js";
|
|
6
6
|
/** Default timeout for long-poll getUpdates requests. */
|
|
7
7
|
const DEFAULT_LONG_POLL_TIMEOUT_MS = 35_000;
|
|
8
8
|
/** Default timeout for regular API requests (sendMessage, getUploadUrl). */
|
|
@@ -18,10 +18,10 @@ function randomWechatUin() {
|
|
|
18
18
|
return Buffer.from(String(uint32), "utf-8").toString("base64");
|
|
19
19
|
}
|
|
20
20
|
/** Build headers shared by both GET and POST requests. */
|
|
21
|
-
function buildCommonHeaders() {
|
|
21
|
+
function buildCommonHeaders(metadata) {
|
|
22
22
|
const headers = {
|
|
23
23
|
"iLink-App-Id": ILINK_APP_ID,
|
|
24
|
-
"iLink-App-ClientVersion": String(
|
|
24
|
+
"iLink-App-ClientVersion": String(metadata.appClientVersion),
|
|
25
25
|
};
|
|
26
26
|
return headers;
|
|
27
27
|
}
|
|
@@ -30,7 +30,7 @@ function buildHeaders(opts) {
|
|
|
30
30
|
"Content-Type": "application/json",
|
|
31
31
|
AuthorizationType: "ilink_bot_token",
|
|
32
32
|
"X-WECHAT-UIN": randomWechatUin(),
|
|
33
|
-
...buildCommonHeaders(),
|
|
33
|
+
...buildCommonHeaders(opts.metadata),
|
|
34
34
|
};
|
|
35
35
|
if (opts.token?.trim()) {
|
|
36
36
|
headers.Authorization = `Bearer ${opts.token.trim()}`;
|
|
@@ -47,18 +47,19 @@ function buildHeaders(opts) {
|
|
|
47
47
|
export async function apiGetFetch(params) {
|
|
48
48
|
const base = ensureTrailingSlash(params.baseUrl);
|
|
49
49
|
const url = new URL(params.endpoint, base);
|
|
50
|
-
const hdrs = buildCommonHeaders();
|
|
50
|
+
const hdrs = buildCommonHeaders(params.metadata);
|
|
51
51
|
logger.debug(`GET ${redactUrl(url.toString())}`);
|
|
52
52
|
const timeoutMs = params.timeoutMs;
|
|
53
53
|
const controller = timeoutMs != null && timeoutMs > 0 ? new AbortController() : undefined;
|
|
54
54
|
const t = controller != null && timeoutMs != null
|
|
55
55
|
? setTimeout(() => controller.abort(), timeoutMs)
|
|
56
56
|
: undefined;
|
|
57
|
+
const { signal, cleanup } = combineAbortSignals(controller, params.abortSignal);
|
|
57
58
|
try {
|
|
58
59
|
const res = await fetch(url.toString(), {
|
|
59
60
|
method: "GET",
|
|
60
61
|
headers: hdrs,
|
|
61
|
-
...(
|
|
62
|
+
...(signal ? { signal } : {}),
|
|
62
63
|
});
|
|
63
64
|
if (t !== undefined)
|
|
64
65
|
clearTimeout(t);
|
|
@@ -74,6 +75,9 @@ export async function apiGetFetch(params) {
|
|
|
74
75
|
clearTimeout(t);
|
|
75
76
|
throw err;
|
|
76
77
|
}
|
|
78
|
+
finally {
|
|
79
|
+
cleanup();
|
|
80
|
+
}
|
|
77
81
|
}
|
|
78
82
|
function combineAbortSignals(internal, external) {
|
|
79
83
|
if (!internal && !external)
|
|
@@ -103,7 +107,7 @@ function combineAbortSignals(internal, external) {
|
|
|
103
107
|
export async function apiPostFetch(params) {
|
|
104
108
|
const base = ensureTrailingSlash(params.baseUrl);
|
|
105
109
|
const url = new URL(params.endpoint, base);
|
|
106
|
-
const hdrs = buildHeaders({ token: params.token });
|
|
110
|
+
const hdrs = buildHeaders({ metadata: params.metadata, token: params.token });
|
|
107
111
|
logger.debug(`POST ${redactUrl(url.toString())} body=${redactBody(params.body)}`);
|
|
108
112
|
const controller = params.timeoutMs !== undefined ? new AbortController() : undefined;
|
|
109
113
|
const t = controller != null && params.timeoutMs !== undefined
|
|
@@ -144,10 +148,11 @@ export async function getUpdates(params) {
|
|
|
144
148
|
try {
|
|
145
149
|
const rawText = await apiPostFetch({
|
|
146
150
|
baseUrl: params.baseUrl,
|
|
151
|
+
metadata: params.metadata,
|
|
147
152
|
endpoint: "ilink/bot/getupdates",
|
|
148
153
|
body: JSON.stringify({
|
|
149
154
|
get_updates_buf: params.get_updates_buf ?? "",
|
|
150
|
-
base_info: buildBaseInfo(),
|
|
155
|
+
base_info: params.metadata.buildBaseInfo(),
|
|
151
156
|
}),
|
|
152
157
|
token: params.token,
|
|
153
158
|
timeoutMs: timeout,
|
|
@@ -179,6 +184,7 @@ export async function getUpdates(params) {
|
|
|
179
184
|
export async function getUploadUrl(params) {
|
|
180
185
|
const rawText = await apiPostFetch({
|
|
181
186
|
baseUrl: params.baseUrl,
|
|
187
|
+
metadata: params.metadata,
|
|
182
188
|
endpoint: "ilink/bot/getuploadurl",
|
|
183
189
|
body: JSON.stringify({
|
|
184
190
|
filekey: params.filekey,
|
|
@@ -192,7 +198,7 @@ export async function getUploadUrl(params) {
|
|
|
192
198
|
thumb_filesize: params.thumb_filesize,
|
|
193
199
|
no_need_thumb: params.no_need_thumb,
|
|
194
200
|
aeskey: params.aeskey,
|
|
195
|
-
base_info: buildBaseInfo(),
|
|
201
|
+
base_info: params.metadata.buildBaseInfo(),
|
|
196
202
|
}),
|
|
197
203
|
token: params.token,
|
|
198
204
|
timeoutMs: params.timeoutMs ?? DEFAULT_API_TIMEOUT_MS,
|
|
@@ -205,8 +211,9 @@ export async function getUploadUrl(params) {
|
|
|
205
211
|
export async function sendMessage(params) {
|
|
206
212
|
await apiPostFetch({
|
|
207
213
|
baseUrl: params.baseUrl,
|
|
214
|
+
metadata: params.metadata,
|
|
208
215
|
endpoint: "ilink/bot/sendmessage",
|
|
209
|
-
body: JSON.stringify({ ...params.body, base_info: buildBaseInfo() }),
|
|
216
|
+
body: JSON.stringify({ ...params.body, base_info: params.metadata.buildBaseInfo() }),
|
|
210
217
|
token: params.token,
|
|
211
218
|
timeoutMs: params.timeoutMs ?? DEFAULT_API_TIMEOUT_MS,
|
|
212
219
|
label: "sendMessage",
|
|
@@ -216,11 +223,12 @@ export async function sendMessage(params) {
|
|
|
216
223
|
export async function getConfig(params) {
|
|
217
224
|
const rawText = await apiPostFetch({
|
|
218
225
|
baseUrl: params.baseUrl,
|
|
226
|
+
metadata: params.metadata,
|
|
219
227
|
endpoint: "ilink/bot/getconfig",
|
|
220
228
|
body: JSON.stringify({
|
|
221
229
|
ilink_user_id: params.ilinkUserId,
|
|
222
230
|
context_token: params.contextToken,
|
|
223
|
-
base_info: buildBaseInfo(),
|
|
231
|
+
base_info: params.metadata.buildBaseInfo(),
|
|
224
232
|
}),
|
|
225
233
|
token: params.token,
|
|
226
234
|
timeoutMs: params.timeoutMs ?? DEFAULT_CONFIG_TIMEOUT_MS,
|
|
@@ -233,8 +241,9 @@ export async function getConfig(params) {
|
|
|
233
241
|
export async function sendTyping(params) {
|
|
234
242
|
await apiPostFetch({
|
|
235
243
|
baseUrl: params.baseUrl,
|
|
244
|
+
metadata: params.metadata,
|
|
236
245
|
endpoint: "ilink/bot/sendtyping",
|
|
237
|
-
body: JSON.stringify({ ...params.body, base_info: buildBaseInfo() }),
|
|
246
|
+
body: JSON.stringify({ ...params.body, base_info: params.metadata.buildBaseInfo() }),
|
|
238
247
|
token: params.token,
|
|
239
248
|
timeoutMs: params.timeoutMs ?? DEFAULT_CONFIG_TIMEOUT_MS,
|
|
240
249
|
label: "sendTyping",
|
|
@@ -248,8 +257,9 @@ export async function sendTyping(params) {
|
|
|
248
257
|
export async function notifyStop(params) {
|
|
249
258
|
const rawText = await apiPostFetch({
|
|
250
259
|
baseUrl: params.baseUrl,
|
|
260
|
+
metadata: params.metadata,
|
|
251
261
|
endpoint: "ilink/bot/msg/notifystop",
|
|
252
|
-
body: JSON.stringify({ base_info: buildBaseInfo() }),
|
|
262
|
+
body: JSON.stringify({ base_info: params.metadata.buildBaseInfo() }),
|
|
253
263
|
token: params.token,
|
|
254
264
|
timeoutMs: params.timeoutMs ?? DEFAULT_CONFIG_TIMEOUT_MS,
|
|
255
265
|
label: "notifyStop",
|
|
@@ -262,12 +272,12 @@ export async function notifyStop(params) {
|
|
|
262
272
|
export async function notifyStart(params) {
|
|
263
273
|
const rawText = await apiPostFetch({
|
|
264
274
|
baseUrl: params.baseUrl,
|
|
275
|
+
metadata: params.metadata,
|
|
265
276
|
endpoint: "ilink/bot/msg/notifystart",
|
|
266
|
-
body: JSON.stringify({ base_info: buildBaseInfo() }),
|
|
277
|
+
body: JSON.stringify({ base_info: params.metadata.buildBaseInfo() }),
|
|
267
278
|
token: params.token,
|
|
268
279
|
timeoutMs: params.timeoutMs ?? DEFAULT_CONFIG_TIMEOUT_MS,
|
|
269
280
|
label: "notifyStart",
|
|
270
281
|
});
|
|
271
282
|
return JSON.parse(rawText);
|
|
272
283
|
}
|
|
273
|
-
//# sourceMappingURL=ilink-api.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type WeixinApiOptions } from "./ilink-api.js";
|
|
1
2
|
/** Subset of getConfig fields that we actually need; add new fields here as needed. */
|
|
2
3
|
export interface CachedConfig {
|
|
3
4
|
typingTicket: string;
|
|
@@ -7,13 +8,7 @@ export interface CachedConfig {
|
|
|
7
8
|
* exponential-backoff retry (up to 1h) on failure.
|
|
8
9
|
*/
|
|
9
10
|
export declare class WeixinConfigManager {
|
|
10
|
-
private
|
|
11
|
-
|
|
12
|
-
private cache;
|
|
13
|
-
constructor(apiOpts: {
|
|
14
|
-
baseUrl: string;
|
|
15
|
-
token?: string;
|
|
16
|
-
}, log: (msg: string) => void);
|
|
11
|
+
#private;
|
|
12
|
+
constructor(apiOptions: WeixinApiOptions, log: (msg: string) => void);
|
|
17
13
|
getForUser(userId: string, contextToken?: string): Promise<CachedConfig>;
|
|
18
14
|
}
|
|
19
|
-
//# sourceMappingURL=ilink-config-cache.d.ts.map
|
|
@@ -7,39 +7,38 @@ const CONFIG_CACHE_MAX_RETRY_MS = 60 * 60 * 1000;
|
|
|
7
7
|
* exponential-backoff retry (up to 1h) on failure.
|
|
8
8
|
*/
|
|
9
9
|
export class WeixinConfigManager {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
constructor(
|
|
14
|
-
this
|
|
15
|
-
this
|
|
10
|
+
#cache = new Map();
|
|
11
|
+
#apiOptions;
|
|
12
|
+
#log;
|
|
13
|
+
constructor(apiOptions, log) {
|
|
14
|
+
this.#apiOptions = apiOptions;
|
|
15
|
+
this.#log = log;
|
|
16
16
|
}
|
|
17
17
|
async getForUser(userId, contextToken) {
|
|
18
18
|
const now = Date.now();
|
|
19
|
-
const entry = this
|
|
19
|
+
const entry = this.#cache.get(userId);
|
|
20
20
|
const shouldFetch = !entry || now >= entry.nextFetchAt;
|
|
21
21
|
if (shouldFetch) {
|
|
22
22
|
let fetchOk = false;
|
|
23
23
|
try {
|
|
24
24
|
const resp = await getConfig({
|
|
25
|
-
|
|
26
|
-
token: this.apiOpts.token,
|
|
25
|
+
...this.#apiOptions,
|
|
27
26
|
ilinkUserId: userId,
|
|
28
27
|
contextToken,
|
|
29
28
|
});
|
|
30
29
|
if (resp.ret === 0) {
|
|
31
|
-
this
|
|
30
|
+
this.#cache.set(userId, {
|
|
32
31
|
config: { typingTicket: resp.typing_ticket ?? "" },
|
|
33
32
|
everSucceeded: true,
|
|
34
33
|
nextFetchAt: now + Math.random() * CONFIG_CACHE_TTL_MS,
|
|
35
34
|
retryDelayMs: CONFIG_CACHE_INITIAL_RETRY_MS,
|
|
36
35
|
});
|
|
37
|
-
this
|
|
36
|
+
this.#log(`[weixin] config ${entry?.everSucceeded ? "refreshed" : "cached"} for ${userId}`);
|
|
38
37
|
fetchOk = true;
|
|
39
38
|
}
|
|
40
39
|
}
|
|
41
40
|
catch (err) {
|
|
42
|
-
this
|
|
41
|
+
this.#log(`[weixin] getConfig failed for ${userId} (ignored): ${String(err)}`);
|
|
43
42
|
}
|
|
44
43
|
if (!fetchOk) {
|
|
45
44
|
const prevDelay = entry?.retryDelayMs ?? CONFIG_CACHE_INITIAL_RETRY_MS;
|
|
@@ -49,7 +48,7 @@ export class WeixinConfigManager {
|
|
|
49
48
|
entry.retryDelayMs = nextDelay;
|
|
50
49
|
}
|
|
51
50
|
else {
|
|
52
|
-
this
|
|
51
|
+
this.#cache.set(userId, {
|
|
53
52
|
config: { typingTicket: "" },
|
|
54
53
|
everSucceeded: false,
|
|
55
54
|
nextFetchAt: now + CONFIG_CACHE_INITIAL_RETRY_MS,
|
|
@@ -58,7 +57,6 @@ export class WeixinConfigManager {
|
|
|
58
57
|
}
|
|
59
58
|
}
|
|
60
59
|
}
|
|
61
|
-
return this
|
|
60
|
+
return this.#cache.get(userId)?.config ?? { typingTicket: "" };
|
|
62
61
|
}
|
|
63
62
|
}
|
|
64
|
-
//# sourceMappingURL=ilink-config-cache.js.map
|
package/lib/ilink-logger.d.ts
CHANGED
|
@@ -5,6 +5,4 @@ export type IlinkLogger = {
|
|
|
5
5
|
error: (msg: string) => void;
|
|
6
6
|
withAccount: (accountId: string) => IlinkLogger;
|
|
7
7
|
};
|
|
8
|
-
export declare function setIlinkLogger(next: IlinkLogger): void;
|
|
9
8
|
export declare const logger: IlinkLogger;
|
|
10
|
-
//# sourceMappingURL=ilink-logger.d.ts.map
|
package/lib/ilink-logger.js
CHANGED
|
@@ -1,27 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
warn: (msg) => sink.warn(`${prefix} ${msg}`),
|
|
12
|
-
error: (msg) => sink.error(`${prefix} ${msg}`),
|
|
13
|
-
withAccount: () => this,
|
|
14
|
-
};
|
|
15
|
-
},
|
|
16
|
-
};
|
|
17
|
-
export function setIlinkLogger(next) {
|
|
18
|
-
sink = next;
|
|
1
|
+
function createIlinkLogger(prefix = '[weixin-ilink]') {
|
|
2
|
+
return Object.freeze({
|
|
3
|
+
debug: () => { },
|
|
4
|
+
info: () => { },
|
|
5
|
+
warn: (msg) => console.warn(`${prefix} ${msg}`),
|
|
6
|
+
error: (msg) => console.error(`${prefix} ${msg}`),
|
|
7
|
+
withAccount(accountId) {
|
|
8
|
+
return createIlinkLogger(`[weixin-ilink:${accountId}]`);
|
|
9
|
+
},
|
|
10
|
+
});
|
|
19
11
|
}
|
|
20
|
-
export const logger =
|
|
21
|
-
debug: (msg) => sink.debug(msg),
|
|
22
|
-
info: (msg) => sink.info(msg),
|
|
23
|
-
warn: (msg) => sink.warn(msg),
|
|
24
|
-
error: (msg) => sink.error(msg),
|
|
25
|
-
withAccount: (accountId) => sink.withAccount(accountId),
|
|
26
|
-
};
|
|
27
|
-
//# sourceMappingURL=ilink-logger.js.map
|
|
12
|
+
export const logger = createIlinkLogger();
|
package/lib/ilink-meta.d.ts
CHANGED
|
@@ -2,12 +2,17 @@ import type { BaseInfo } from "./ilink-types.js";
|
|
|
2
2
|
export declare const ILINK_APP_ID = "bot";
|
|
3
3
|
export declare const DEFAULT_CDN_BASE_URL = "https://novac2c.cdn.weixin.qq.com/c2c";
|
|
4
4
|
export declare const DEFAULT_API_BASE_URL = "https://ilinkai.weixin.qq.com";
|
|
5
|
-
export declare function configureIlinkMeta(opts: {
|
|
6
|
-
botAgent?: string;
|
|
7
|
-
version?: string;
|
|
8
|
-
}): void;
|
|
9
5
|
export declare function buildClientVersion(version: string): number;
|
|
10
|
-
export declare
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
export declare function sanitizeBotAgent(raw: string | undefined, fallback?: string): string;
|
|
7
|
+
export interface IlinkClientMetadataOptions {
|
|
8
|
+
readonly botAgent?: string;
|
|
9
|
+
readonly version?: string;
|
|
10
|
+
}
|
|
11
|
+
/** Immutable request identity owned by one endpoint. */
|
|
12
|
+
export declare class IlinkClientMetadata {
|
|
13
|
+
readonly channelVersion: string;
|
|
14
|
+
readonly botAgent: string;
|
|
15
|
+
readonly appClientVersion: number;
|
|
16
|
+
constructor(options?: IlinkClientMetadataOptions);
|
|
17
|
+
buildBaseInfo(): BaseInfo;
|
|
18
|
+
}
|
package/lib/ilink-meta.js
CHANGED
|
@@ -6,8 +6,6 @@ export const DEFAULT_CDN_BASE_URL = "https://novac2c.cdn.weixin.qq.com/c2c";
|
|
|
6
6
|
export const DEFAULT_API_BASE_URL = "https://ilinkai.weixin.qq.com";
|
|
7
7
|
const DEFAULT_BOT_AGENT = "Zhin.js";
|
|
8
8
|
const BOT_AGENT_MAX_LEN = 256;
|
|
9
|
-
let channelVersion = "0.0.0";
|
|
10
|
-
let configuredBotAgent;
|
|
11
9
|
function readPackageVersion() {
|
|
12
10
|
try {
|
|
13
11
|
const pkgPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "../package.json");
|
|
@@ -18,13 +16,7 @@ function readPackageVersion() {
|
|
|
18
16
|
return "0.0.0";
|
|
19
17
|
}
|
|
20
18
|
}
|
|
21
|
-
|
|
22
|
-
export function configureIlinkMeta(opts) {
|
|
23
|
-
if (opts.botAgent)
|
|
24
|
-
configuredBotAgent = opts.botAgent;
|
|
25
|
-
if (opts.version)
|
|
26
|
-
channelVersion = opts.version;
|
|
27
|
-
}
|
|
19
|
+
const PACKAGE_VERSION = readPackageVersion();
|
|
28
20
|
export function buildClientVersion(version) {
|
|
29
21
|
const parts = version.split(".").map((p) => parseInt(p, 10));
|
|
30
22
|
const major = parts[0] ?? 0;
|
|
@@ -32,26 +24,37 @@ export function buildClientVersion(version) {
|
|
|
32
24
|
const patch = parts[2] ?? 0;
|
|
33
25
|
return ((major & 0xff) << 16) | ((minor & 0xff) << 8) | (patch & 0xff);
|
|
34
26
|
}
|
|
35
|
-
export
|
|
36
|
-
export function sanitizeBotAgent(raw) {
|
|
27
|
+
export function sanitizeBotAgent(raw, fallback = DEFAULT_BOT_AGENT) {
|
|
37
28
|
if (!raw || typeof raw !== "string")
|
|
38
|
-
return
|
|
29
|
+
return fallback;
|
|
39
30
|
const trimmed = raw.trim();
|
|
40
31
|
if (!trimmed)
|
|
41
|
-
return
|
|
32
|
+
return fallback;
|
|
42
33
|
const productRe = /^[A-Za-z0-9_.\-]{1,32}\/[A-Za-z0-9_.+\-]{1,32}$/;
|
|
43
34
|
const tokens = trimmed.split(/\s+/).filter((tok) => productRe.test(tok));
|
|
44
35
|
if (tokens.length === 0)
|
|
45
|
-
return
|
|
36
|
+
return fallback;
|
|
46
37
|
const joined = tokens.join(" ");
|
|
47
38
|
if (Buffer.byteLength(joined, "utf-8") <= BOT_AGENT_MAX_LEN)
|
|
48
39
|
return joined;
|
|
49
|
-
return
|
|
40
|
+
return fallback;
|
|
50
41
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
42
|
+
/** Immutable request identity owned by one endpoint. */
|
|
43
|
+
export class IlinkClientMetadata {
|
|
44
|
+
channelVersion;
|
|
45
|
+
botAgent;
|
|
46
|
+
appClientVersion;
|
|
47
|
+
constructor(options = {}) {
|
|
48
|
+
this.channelVersion = options.version ?? PACKAGE_VERSION;
|
|
49
|
+
const defaultBotAgent = `Zhin.js/${this.channelVersion}`;
|
|
50
|
+
this.botAgent = sanitizeBotAgent(options.botAgent, defaultBotAgent);
|
|
51
|
+
this.appClientVersion = buildClientVersion(this.channelVersion);
|
|
52
|
+
Object.freeze(this);
|
|
53
|
+
}
|
|
54
|
+
buildBaseInfo() {
|
|
55
|
+
return {
|
|
56
|
+
channel_version: this.channelVersion,
|
|
57
|
+
bot_agent: this.botAgent,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
56
60
|
}
|
|
57
|
-
//# sourceMappingURL=ilink-meta.js.map
|
package/lib/ilink-random.d.ts
CHANGED
package/lib/ilink-random.js
CHANGED
package/lib/ilink-redact.d.ts
CHANGED
package/lib/ilink-redact.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
/** Error code returned by the server when the bot session has expired. */
|
|
2
1
|
export declare const SESSION_EXPIRED_ERRCODE = -14;
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
//# sourceMappingURL=ilink-session-guard.d.ts.map
|
|
2
|
+
export declare const SESSION_PAUSE_DURATION_MS: number;
|
|
3
|
+
/** Endpoint-owned cooldown state for an authenticated iLink session. */
|
|
4
|
+
export declare class IlinkSessionGuard {
|
|
5
|
+
#private;
|
|
6
|
+
readonly accountId: string;
|
|
7
|
+
private readonly now;
|
|
8
|
+
private readonly pauseDurationMs;
|
|
9
|
+
constructor(accountId: string, now?: () => number, pauseDurationMs?: number);
|
|
10
|
+
pause(): void;
|
|
11
|
+
get paused(): boolean;
|
|
12
|
+
get remainingMs(): number;
|
|
13
|
+
assertActive(): void;
|
|
14
|
+
}
|
|
@@ -1,49 +1,35 @@
|
|
|
1
|
-
import { logger } from
|
|
2
|
-
const SESSION_PAUSE_DURATION_MS = 60 * 60 * 1000;
|
|
3
|
-
/** Error code returned by the server when the bot session has expired. */
|
|
1
|
+
import { logger } from './ilink-logger.js';
|
|
4
2
|
export const SESSION_EXPIRED_ERRCODE = -14;
|
|
5
|
-
const
|
|
6
|
-
/**
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return false;
|
|
17
|
-
if (Date.now() >= until) {
|
|
18
|
-
pauseUntilMap.delete(accountId);
|
|
19
|
-
return false;
|
|
3
|
+
export const SESSION_PAUSE_DURATION_MS = 60 * 60 * 1000;
|
|
4
|
+
/** Endpoint-owned cooldown state for an authenticated iLink session. */
|
|
5
|
+
export class IlinkSessionGuard {
|
|
6
|
+
accountId;
|
|
7
|
+
now;
|
|
8
|
+
pauseDurationMs;
|
|
9
|
+
#pauseUntil = 0;
|
|
10
|
+
constructor(accountId, now = Date.now, pauseDurationMs = SESSION_PAUSE_DURATION_MS) {
|
|
11
|
+
this.accountId = accountId;
|
|
12
|
+
this.now = now;
|
|
13
|
+
this.pauseDurationMs = pauseDurationMs;
|
|
20
14
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
15
|
+
pause() {
|
|
16
|
+
this.#pauseUntil = this.now() + this.pauseDurationMs;
|
|
17
|
+
logger.info(`session-guard: paused accountId=${this.accountId} until=${new Date(this.#pauseUntil).toISOString()} (${this.pauseDurationMs / 1000}s)`);
|
|
18
|
+
}
|
|
19
|
+
get paused() {
|
|
20
|
+
return this.remainingMs > 0;
|
|
21
|
+
}
|
|
22
|
+
get remainingMs() {
|
|
23
|
+
const remaining = this.#pauseUntil - this.now();
|
|
24
|
+
if (remaining > 0)
|
|
25
|
+
return remaining;
|
|
26
|
+
this.#pauseUntil = 0;
|
|
31
27
|
return 0;
|
|
32
28
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const remainingMin = Math.ceil(getRemainingPauseMs(accountId) / 60_000);
|
|
39
|
-
throw new Error(`session paused for accountId=${accountId}, ${remainingMin} min remaining (errcode ${SESSION_EXPIRED_ERRCODE})`);
|
|
29
|
+
assertActive() {
|
|
30
|
+
const remaining = this.remainingMs;
|
|
31
|
+
if (remaining === 0)
|
|
32
|
+
return;
|
|
33
|
+
throw new Error(`session paused for accountId=${this.accountId}, ${Math.ceil(remaining / 60_000)} min remaining (errcode ${SESSION_EXPIRED_ERRCODE})`);
|
|
40
34
|
}
|
|
41
35
|
}
|
|
42
|
-
/**
|
|
43
|
-
* Reset internal state — only for tests.
|
|
44
|
-
* @internal
|
|
45
|
-
*/
|
|
46
|
-
export function _resetForTest() {
|
|
47
|
-
pauseUntilMap.clear();
|
|
48
|
-
}
|
|
49
|
-
//# sourceMappingURL=ilink-session-guard.js.map
|
package/lib/ilink-types.d.ts
CHANGED
package/lib/ilink-types.js
CHANGED
package/lib/index.d.ts
CHANGED
|
@@ -1,20 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
interface Adapters {
|
|
12
|
-
"weixin-ilink": WeixinIlinkAdapter;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
export * from "./types.js";
|
|
16
|
-
export { WeixinIlinkEndpoint } from "./endpoint.js";
|
|
17
|
-
export { WeixinIlinkAdapter } from "./adapter.js";
|
|
18
|
-
export { WeixinIlinkTypingIndicatorManager, enableTypingIndicator, type WeixinIlinkTypingIndicatorConfig, } from "./typing-indicator.js";
|
|
19
|
-
export { registerLoginAssistRoutes } from "./login-assist-routes.js";
|
|
20
|
-
//# sourceMappingURL=index.d.ts.map
|
|
1
|
+
export { formatInboundContent, formatOutboundSegments, inboundMessageId, resolveWeixinIlinkConfig, segmentLocalPath, sleep, type ResolvedWeixinIlinkConfig, type WeixinIlinkEndpointConfig, type WeixinInboundMediaPaths, type WeixinMessageWithMedia, type WeixinWireSegment, } from './protocol.js';
|
|
2
|
+
export type { WeixinMessage, MessageItem } from './ilink-types.js';
|
|
3
|
+
export { WeixinIlinkStateStore, type WeixinIlinkCredentials, } from './credentials.js';
|
|
4
|
+
export { IlinkClientMetadata, sanitizeBotAgent, type IlinkClientMetadataOptions, } from './ilink-meta.js';
|
|
5
|
+
export { WeixinContextTokenStore } from './context-store.js';
|
|
6
|
+
export { IlinkSessionGuard, SESSION_EXPIRED_ERRCODE, SESSION_PAUSE_DURATION_MS, } from './ilink-session-guard.js';
|
|
7
|
+
export { WeixinIlinkClient, weixinIlinkClient, type WeixinIlinkClientEventMap, } from './client.js';
|
|
8
|
+
export { WeixinIlinkEndpoint, type WeixinIlinkEndpointOptions, type WeixinIlinkGetUpdates, type WeixinIlinkNotifyStart, type WeixinIlinkNotifyStop, type WeixinIlinkSendText, } from './endpoint.js';
|