wechat-bridge-opencode 0.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/.opencode/tools/send-wechat.ts +88 -0
- package/LICENSE +21 -0
- package/README.md +244 -0
- package/dist/bin/wechat-acp.d.ts +14 -0
- package/dist/bin/wechat-acp.d.ts.map +1 -0
- package/dist/bin/wechat-acp.js +284 -0
- package/dist/bin/wechat-acp.js.map +1 -0
- package/dist/bin/wechat-opencode.d.ts +14 -0
- package/dist/bin/wechat-opencode.d.ts.map +1 -0
- package/dist/bin/wechat-opencode.js +284 -0
- package/dist/bin/wechat-opencode.js.map +1 -0
- package/dist/package.json +60 -0
- package/dist/src/acp/agent-manager.d.ts +23 -0
- package/dist/src/acp/agent-manager.d.ts.map +1 -0
- package/dist/src/acp/agent-manager.js +134 -0
- package/dist/src/acp/agent-manager.js.map +1 -0
- package/dist/src/acp/client.d.ts +49 -0
- package/dist/src/acp/client.d.ts.map +1 -0
- package/dist/src/acp/client.js +219 -0
- package/dist/src/acp/client.js.map +1 -0
- package/dist/src/acp/opencode-sessions.d.ts +17 -0
- package/dist/src/acp/opencode-sessions.d.ts.map +1 -0
- package/dist/src/acp/opencode-sessions.js +59 -0
- package/dist/src/acp/opencode-sessions.js.map +1 -0
- package/dist/src/acp/session.d.ts +75 -0
- package/dist/src/acp/session.d.ts.map +1 -0
- package/dist/src/acp/session.js +273 -0
- package/dist/src/acp/session.js.map +1 -0
- package/dist/src/acp/workspace-manager.d.ts +48 -0
- package/dist/src/acp/workspace-manager.d.ts.map +1 -0
- package/dist/src/acp/workspace-manager.js +141 -0
- package/dist/src/acp/workspace-manager.js.map +1 -0
- package/dist/src/adapter/inbound.d.ts +10 -0
- package/dist/src/adapter/inbound.d.ts.map +1 -0
- package/dist/src/adapter/inbound.js +149 -0
- package/dist/src/adapter/inbound.js.map +1 -0
- package/dist/src/adapter/outbound.d.ts +9 -0
- package/dist/src/adapter/outbound.d.ts.map +1 -0
- package/dist/src/adapter/outbound.js +25 -0
- package/dist/src/adapter/outbound.js.map +1 -0
- package/dist/src/adapter/workspace-cmd.d.ts +53 -0
- package/dist/src/adapter/workspace-cmd.d.ts.map +1 -0
- package/dist/src/adapter/workspace-cmd.js +172 -0
- package/dist/src/adapter/workspace-cmd.js.map +1 -0
- package/dist/src/bridge.d.ts +42 -0
- package/dist/src/bridge.d.ts.map +1 -0
- package/dist/src/bridge.js +557 -0
- package/dist/src/bridge.js.map +1 -0
- package/dist/src/config.d.ts +63 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +88 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/index.d.ts +7 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/weixin/api.d.ts +50 -0
- package/dist/src/weixin/api.d.ts.map +1 -0
- package/dist/src/weixin/api.js +87 -0
- package/dist/src/weixin/api.js.map +1 -0
- package/dist/src/weixin/auth.d.ts +20 -0
- package/dist/src/weixin/auth.d.ts.map +1 -0
- package/dist/src/weixin/auth.js +88 -0
- package/dist/src/weixin/auth.js.map +1 -0
- package/dist/src/weixin/media.d.ts +24 -0
- package/dist/src/weixin/media.d.ts.map +1 -0
- package/dist/src/weixin/media.js +91 -0
- package/dist/src/weixin/media.js.map +1 -0
- package/dist/src/weixin/monitor.d.ts +16 -0
- package/dist/src/weixin/monitor.d.ts.map +1 -0
- package/dist/src/weixin/monitor.js +111 -0
- package/dist/src/weixin/monitor.js.map +1 -0
- package/dist/src/weixin/send.d.ts +32 -0
- package/dist/src/weixin/send.d.ts.map +1 -0
- package/dist/src/weixin/send.js +193 -0
- package/dist/src/weixin/send.js.map +1 -0
- package/dist/src/weixin/types.d.ts +150 -0
- package/dist/src/weixin/types.d.ts.map +1 -0
- package/dist/src/weixin/types.js +33 -0
- package/dist/src/weixin/types.js.map +1 -0
- package/package.json +60 -0
- package/scripts/install-tool.mjs +14 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration types and defaults for wechat-opencode.
|
|
3
|
+
*/
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
export const BUILT_IN_AGENTS = {
|
|
7
|
+
opencode: {
|
|
8
|
+
label: "OpenCode",
|
|
9
|
+
command: "npx",
|
|
10
|
+
args: ["opencode-ai", "acp"],
|
|
11
|
+
description: "OpenCode",
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
export function defaultStorageDir() {
|
|
15
|
+
return path.join(os.homedir(), ".wechat-opencode");
|
|
16
|
+
}
|
|
17
|
+
export function defaultTempDir(storageDir) {
|
|
18
|
+
return path.join(storageDir, "tempfile");
|
|
19
|
+
}
|
|
20
|
+
export function defaultConfig() {
|
|
21
|
+
const storageDir = defaultStorageDir();
|
|
22
|
+
return {
|
|
23
|
+
wechat: {
|
|
24
|
+
baseUrl: "https://ilinkai.weixin.qq.com",
|
|
25
|
+
cdnBaseUrl: "https://novac2c.cdn.weixin.qq.com/c2c",
|
|
26
|
+
botType: "3",
|
|
27
|
+
},
|
|
28
|
+
agent: {
|
|
29
|
+
preset: undefined,
|
|
30
|
+
command: "",
|
|
31
|
+
args: [],
|
|
32
|
+
cwd: process.cwd(),
|
|
33
|
+
showThoughts: false,
|
|
34
|
+
},
|
|
35
|
+
agents: { ...BUILT_IN_AGENTS },
|
|
36
|
+
session: {
|
|
37
|
+
idleTimeoutMs: 1440 * 60_000, // 24 hours
|
|
38
|
+
maxConcurrentUsers: 10,
|
|
39
|
+
},
|
|
40
|
+
daemon: {
|
|
41
|
+
enabled: false,
|
|
42
|
+
logFile: path.join(storageDir, "wechat-opencode.log"),
|
|
43
|
+
pidFile: path.join(storageDir, "daemon.pid"),
|
|
44
|
+
},
|
|
45
|
+
storage: {
|
|
46
|
+
dir: storageDir,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Parse agent string like "claude code" or "npx tsx ./agent.ts"
|
|
52
|
+
* into { command, args }.
|
|
53
|
+
*/
|
|
54
|
+
export function parseAgentCommand(agentStr) {
|
|
55
|
+
const parts = agentStr.trim().split(/\s+/);
|
|
56
|
+
if (parts.length === 0 || !parts[0]) {
|
|
57
|
+
throw new Error("Agent command cannot be empty");
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
command: parts[0],
|
|
61
|
+
args: parts.slice(1),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
export function resolveAgentSelection(agentSelection, registry = BUILT_IN_AGENTS) {
|
|
65
|
+
const preset = registry[agentSelection];
|
|
66
|
+
if (preset) {
|
|
67
|
+
return {
|
|
68
|
+
id: agentSelection,
|
|
69
|
+
label: preset.label,
|
|
70
|
+
command: preset.command,
|
|
71
|
+
args: [...preset.args],
|
|
72
|
+
env: preset.env ? { ...preset.env } : undefined,
|
|
73
|
+
source: "preset",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const parsed = parseAgentCommand(agentSelection);
|
|
77
|
+
return {
|
|
78
|
+
command: parsed.command,
|
|
79
|
+
args: parsed.args,
|
|
80
|
+
source: "raw",
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function listBuiltInAgents(registry = BUILT_IN_AGENTS) {
|
|
84
|
+
return Object.entries(registry)
|
|
85
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
86
|
+
.map(([id, preset]) => ({ id, preset }));
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wechat-opencode — public API
|
|
3
|
+
*/
|
|
4
|
+
export { WeChatOpencodeBridge } from "./bridge.js";
|
|
5
|
+
export type { AgentCommandConfig, AgentPreset, ResolvedAgentConfig, WeChatOpencodeConfig, } from "./config.js";
|
|
6
|
+
export { BUILT_IN_AGENTS, defaultConfig, listBuiltInAgents, parseAgentCommand, resolveAgentSelection, } from "./config.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,YAAY,EACX,kBAAkB,EAClB,WAAW,EACX,mBAAmB,EACnB,oBAAoB,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,GACrB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAOnD,OAAO,EACN,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,GACrB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeChat iLink HTTP API client.
|
|
3
|
+
* Adapted from @tencent-weixin/openclaw-weixin api/api.ts
|
|
4
|
+
*/
|
|
5
|
+
import type { GetUpdatesResp, SendMessageReq, GetUploadUrlReq, GetUploadUrlResp, SendTypingReq, GetConfigResp } from "./types.js";
|
|
6
|
+
export declare function getUpdates(params: {
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
token?: string;
|
|
9
|
+
get_updates_buf: string;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
}): Promise<GetUpdatesResp>;
|
|
12
|
+
export declare function sendMessage(params: {
|
|
13
|
+
baseUrl: string;
|
|
14
|
+
token?: string;
|
|
15
|
+
body: SendMessageReq;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
export declare function getUploadUrl(params: {
|
|
18
|
+
baseUrl: string;
|
|
19
|
+
token?: string;
|
|
20
|
+
body: GetUploadUrlReq;
|
|
21
|
+
}): Promise<GetUploadUrlResp>;
|
|
22
|
+
export declare function getConfig(params: {
|
|
23
|
+
baseUrl: string;
|
|
24
|
+
token?: string;
|
|
25
|
+
ilinkUserId: string;
|
|
26
|
+
contextToken?: string;
|
|
27
|
+
}): Promise<GetConfigResp>;
|
|
28
|
+
export declare function sendTyping(params: {
|
|
29
|
+
baseUrl: string;
|
|
30
|
+
token?: string;
|
|
31
|
+
body: SendTypingReq;
|
|
32
|
+
}): Promise<void>;
|
|
33
|
+
export declare function getBotQrcode(params: {
|
|
34
|
+
baseUrl: string;
|
|
35
|
+
botType?: string;
|
|
36
|
+
}): Promise<{
|
|
37
|
+
qrcode: string;
|
|
38
|
+
qrcode_img_content: string;
|
|
39
|
+
}>;
|
|
40
|
+
export declare function getQrcodeStatus(params: {
|
|
41
|
+
baseUrl: string;
|
|
42
|
+
qrcode: string;
|
|
43
|
+
}): Promise<{
|
|
44
|
+
status: string;
|
|
45
|
+
bot_token?: string;
|
|
46
|
+
baseurl?: string;
|
|
47
|
+
ilink_bot_id?: string;
|
|
48
|
+
ilink_user_id?: string;
|
|
49
|
+
}>;
|
|
50
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/weixin/api.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAEV,cAAc,EACd,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,aAAa,EACd,MAAM,YAAY,CAAC;AAsEpB,wBAAsB,UAAU,CAAC,MAAM,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,cAAc,CAAC,CAQ1B;AAED,wBAAsB,WAAW,CAAC,MAAM,EAAE;IACxC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,cAAc,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhB;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,eAAe,CAAC;CACvB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAO5B;AAED,wBAAsB,SAAS,CAAC,MAAM,EAAE;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,aAAa,CAAC,CAWzB;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,aAAa,CAAC;CACrB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQhB;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,kBAAkB,EAAE,MAAM,CAAA;CAAE,CAAC,CAK1D;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC;IACV,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC,CAKD"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeChat iLink HTTP API client.
|
|
3
|
+
* Adapted from @tencent-weixin/openclaw-weixin api/api.ts
|
|
4
|
+
*/
|
|
5
|
+
import crypto from "node:crypto";
|
|
6
|
+
const CHANNEL_VERSION = "1.0.2";
|
|
7
|
+
function randomWechatUin() {
|
|
8
|
+
const uint32 = crypto.randomBytes(4).readUInt32BE(0);
|
|
9
|
+
return Buffer.from(String(uint32), "utf-8").toString("base64");
|
|
10
|
+
}
|
|
11
|
+
function buildHeaders(opts) {
|
|
12
|
+
const headers = {
|
|
13
|
+
"Content-Type": "application/json",
|
|
14
|
+
AuthorizationType: "ilink_bot_token",
|
|
15
|
+
"X-WECHAT-UIN": randomWechatUin(),
|
|
16
|
+
};
|
|
17
|
+
if (opts.body) {
|
|
18
|
+
headers["Content-Length"] = String(Buffer.byteLength(opts.body, "utf-8"));
|
|
19
|
+
}
|
|
20
|
+
if (opts.token?.trim()) {
|
|
21
|
+
headers["Authorization"] = `Bearer ${opts.token.trim()}`;
|
|
22
|
+
}
|
|
23
|
+
return headers;
|
|
24
|
+
}
|
|
25
|
+
function buildBaseInfo() {
|
|
26
|
+
return { channel_version: CHANNEL_VERSION };
|
|
27
|
+
}
|
|
28
|
+
async function apiGet(baseUrl, path, token) {
|
|
29
|
+
const url = `${baseUrl.replace(/\/$/, "")}/${path}`;
|
|
30
|
+
const res = await fetch(url, { headers: buildHeaders({ token }) });
|
|
31
|
+
const text = await res.text();
|
|
32
|
+
if (!res.ok)
|
|
33
|
+
throw new Error(`HTTP ${res.status}: ${text}`);
|
|
34
|
+
return JSON.parse(text);
|
|
35
|
+
}
|
|
36
|
+
async function apiPost(baseUrl, endpoint, body, token, timeoutMs = 15_000) {
|
|
37
|
+
const url = `${baseUrl.replace(/\/$/, "")}/${endpoint}`;
|
|
38
|
+
const payload = { ...body, base_info: buildBaseInfo() };
|
|
39
|
+
const bodyStr = JSON.stringify(payload);
|
|
40
|
+
const controller = new AbortController();
|
|
41
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
42
|
+
try {
|
|
43
|
+
const res = await fetch(url, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: buildHeaders({ token, body: bodyStr }),
|
|
46
|
+
body: bodyStr,
|
|
47
|
+
signal: controller.signal,
|
|
48
|
+
});
|
|
49
|
+
clearTimeout(timer);
|
|
50
|
+
const text = await res.text();
|
|
51
|
+
if (!res.ok)
|
|
52
|
+
throw new Error(`HTTP ${res.status}: ${text}`);
|
|
53
|
+
return JSON.parse(text);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
if (err.name === "AbortError") {
|
|
58
|
+
return { ret: 0, msgs: [] };
|
|
59
|
+
}
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export async function getUpdates(params) {
|
|
64
|
+
return apiPost(params.baseUrl, "ilink/bot/getupdates", { get_updates_buf: params.get_updates_buf }, params.token, params.timeoutMs ?? 38_000);
|
|
65
|
+
}
|
|
66
|
+
export async function sendMessage(params) {
|
|
67
|
+
await apiPost(params.baseUrl, "ilink/bot/sendmessage", params.body, params.token);
|
|
68
|
+
}
|
|
69
|
+
export async function getUploadUrl(params) {
|
|
70
|
+
return apiPost(params.baseUrl, "ilink/bot/getuploadurl", params.body, params.token);
|
|
71
|
+
}
|
|
72
|
+
export async function getConfig(params) {
|
|
73
|
+
return apiPost(params.baseUrl, "ilink/bot/getconfig", {
|
|
74
|
+
ilink_user_id: params.ilinkUserId,
|
|
75
|
+
...(params.contextToken ? { context_token: params.contextToken } : {}),
|
|
76
|
+
}, params.token, 10_000);
|
|
77
|
+
}
|
|
78
|
+
export async function sendTyping(params) {
|
|
79
|
+
await apiPost(params.baseUrl, "ilink/bot/sendtyping", params.body, params.token, 10_000);
|
|
80
|
+
}
|
|
81
|
+
export async function getBotQrcode(params) {
|
|
82
|
+
return apiGet(params.baseUrl, `ilink/bot/get_bot_qrcode?bot_type=${params.botType ?? "3"}`);
|
|
83
|
+
}
|
|
84
|
+
export async function getQrcodeStatus(params) {
|
|
85
|
+
return apiGet(params.baseUrl, `ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(params.qrcode)}`);
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/weixin/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeChat QR login flow + token persistence.
|
|
3
|
+
*/
|
|
4
|
+
export interface TokenData {
|
|
5
|
+
token: string;
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
accountId: string;
|
|
8
|
+
userId: string;
|
|
9
|
+
savedAt: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function loadToken(storageDir: string): TokenData | null;
|
|
12
|
+
export declare function saveToken(storageDir: string, data: TokenData): void;
|
|
13
|
+
export declare function login(params: {
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
botType?: string;
|
|
16
|
+
storageDir: string;
|
|
17
|
+
log: (msg: string) => void;
|
|
18
|
+
renderQrUrl?: (url: string) => void;
|
|
19
|
+
}): Promise<TokenData>;
|
|
20
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/weixin/auth.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAUD,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAQ9D;AAED,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAKnE;AAED,wBAAsB,KAAK,CAAC,MAAM,EAAE;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC,GAAG,OAAO,CAAC,SAAS,CAAC,CA+DrB"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeChat QR login flow + token persistence.
|
|
3
|
+
*/
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { getBotQrcode, getQrcodeStatus } from "./api.js";
|
|
7
|
+
function getAuthDir(storageDir) {
|
|
8
|
+
return path.join(storageDir, "auth");
|
|
9
|
+
}
|
|
10
|
+
function getTokenPath(storageDir) {
|
|
11
|
+
return path.join(getAuthDir(storageDir), "token.json");
|
|
12
|
+
}
|
|
13
|
+
export function loadToken(storageDir) {
|
|
14
|
+
const tokenPath = getTokenPath(storageDir);
|
|
15
|
+
if (!fs.existsSync(tokenPath))
|
|
16
|
+
return null;
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(fs.readFileSync(tokenPath, "utf-8"));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function saveToken(storageDir, data) {
|
|
25
|
+
const authDir = getAuthDir(storageDir);
|
|
26
|
+
fs.mkdirSync(authDir, { recursive: true });
|
|
27
|
+
const tokenPath = getTokenPath(storageDir);
|
|
28
|
+
fs.writeFileSync(tokenPath, JSON.stringify(data, null, 2), "utf-8");
|
|
29
|
+
}
|
|
30
|
+
export async function login(params) {
|
|
31
|
+
const { baseUrl, botType, storageDir, log, renderQrUrl } = params;
|
|
32
|
+
log("Starting WeChat QR login...");
|
|
33
|
+
const qrResp = await getBotQrcode({ baseUrl, botType });
|
|
34
|
+
const qrcodeUrl = qrResp.qrcode_img_content;
|
|
35
|
+
log("Please scan the QR code with WeChat:");
|
|
36
|
+
if (renderQrUrl) {
|
|
37
|
+
renderQrUrl(qrcodeUrl);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
log(`QR URL: ${qrcodeUrl}`);
|
|
41
|
+
}
|
|
42
|
+
const deadline = Date.now() + 5 * 60_000;
|
|
43
|
+
let currentQrcode = qrResp.qrcode;
|
|
44
|
+
let refreshCount = 0;
|
|
45
|
+
while (Date.now() < deadline) {
|
|
46
|
+
const statusResp = await getQrcodeStatus({ baseUrl, qrcode: currentQrcode });
|
|
47
|
+
switch (statusResp.status) {
|
|
48
|
+
case "wait":
|
|
49
|
+
break;
|
|
50
|
+
case "scaned":
|
|
51
|
+
log("QR scanned, please confirm in WeChat...");
|
|
52
|
+
break;
|
|
53
|
+
case "expired": {
|
|
54
|
+
refreshCount++;
|
|
55
|
+
if (refreshCount > 3) {
|
|
56
|
+
throw new Error("QR code expired multiple times, please retry");
|
|
57
|
+
}
|
|
58
|
+
log(`QR expired, refreshing (${refreshCount}/3)...`);
|
|
59
|
+
const newQr = await getBotQrcode({ baseUrl, botType });
|
|
60
|
+
currentQrcode = newQr.qrcode;
|
|
61
|
+
if (renderQrUrl) {
|
|
62
|
+
renderQrUrl(newQr.qrcode_img_content);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
log(`New QR URL: ${newQr.qrcode_img_content}`);
|
|
66
|
+
}
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
case "confirmed": {
|
|
70
|
+
log("Login successful!");
|
|
71
|
+
const tokenData = {
|
|
72
|
+
token: statusResp.bot_token,
|
|
73
|
+
baseUrl: statusResp.baseurl || baseUrl,
|
|
74
|
+
accountId: statusResp.ilink_bot_id,
|
|
75
|
+
userId: statusResp.ilink_user_id,
|
|
76
|
+
savedAt: new Date().toISOString(),
|
|
77
|
+
};
|
|
78
|
+
saveToken(storageDir, tokenData);
|
|
79
|
+
log(`Bot ID: ${tokenData.accountId}`);
|
|
80
|
+
log(`Token saved to ${getTokenPath(storageDir)}`);
|
|
81
|
+
return tokenData;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
await new Promise((r) => setTimeout(r, 1500));
|
|
85
|
+
}
|
|
86
|
+
throw new Error("Login timeout (5 minutes)");
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/weixin/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AES-128-ECB encrypt/decrypt for WeChat CDN media.
|
|
3
|
+
* Adapted from @tencent-weixin/openclaw-weixin cdn/aes-ecb.ts
|
|
4
|
+
*/
|
|
5
|
+
import type { CDNMedia } from "./types.js";
|
|
6
|
+
export declare function encryptAesEcb(plaintext: Buffer, key: Buffer): Buffer;
|
|
7
|
+
export declare function decryptAesEcb(ciphertext: Buffer, key: Buffer): Buffer;
|
|
8
|
+
/**
|
|
9
|
+
* Parse the AES key from CDN media reference.
|
|
10
|
+
* The key can be either:
|
|
11
|
+
* - base64 → 16 raw bytes (use directly)
|
|
12
|
+
* - base64 → 32 hex chars → parse hex → 16 bytes
|
|
13
|
+
*/
|
|
14
|
+
export declare function parseAesKey(media: CDNMedia): Buffer | null;
|
|
15
|
+
export declare function downloadAndDecrypt(encryptQueryParam: string, aesKey: Buffer, cdnBaseUrl: string, filekey?: string): Promise<Buffer>;
|
|
16
|
+
export declare function uploadToCdn(params: {
|
|
17
|
+
buffer: Buffer;
|
|
18
|
+
uploadParam: string;
|
|
19
|
+
aesKey: Buffer;
|
|
20
|
+
filekey: string;
|
|
21
|
+
cdnBaseUrl: string;
|
|
22
|
+
uploadUrl?: string;
|
|
23
|
+
}): Promise<string>;
|
|
24
|
+
//# sourceMappingURL=media.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../../src/weixin/media.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGpE;AAED,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGrE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAa1D;AAED,wBAAsB,kBAAkB,CACtC,iBAAiB,EAAE,MAAM,EACzB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,CAAC,CASjB;AAKD,wBAAsB,WAAW,CAAC,MAAM,EAAE;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,CAAC,MAAM,CAAC,CA8ClB"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AES-128-ECB encrypt/decrypt for WeChat CDN media.
|
|
3
|
+
* Adapted from @tencent-weixin/openclaw-weixin cdn/aes-ecb.ts
|
|
4
|
+
*/
|
|
5
|
+
import crypto from "node:crypto";
|
|
6
|
+
export function encryptAesEcb(plaintext, key) {
|
|
7
|
+
const cipher = crypto.createCipheriv("aes-128-ecb", key, null);
|
|
8
|
+
return Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
9
|
+
}
|
|
10
|
+
export function decryptAesEcb(ciphertext, key) {
|
|
11
|
+
const decipher = crypto.createDecipheriv("aes-128-ecb", key, null);
|
|
12
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Parse the AES key from CDN media reference.
|
|
16
|
+
* The key can be either:
|
|
17
|
+
* - base64 → 16 raw bytes (use directly)
|
|
18
|
+
* - base64 → 32 hex chars → parse hex → 16 bytes
|
|
19
|
+
*/
|
|
20
|
+
export function parseAesKey(media) {
|
|
21
|
+
const raw = media.aes_key;
|
|
22
|
+
if (!raw)
|
|
23
|
+
return null;
|
|
24
|
+
const decoded = Buffer.from(raw, "base64");
|
|
25
|
+
if (decoded.length === 16)
|
|
26
|
+
return decoded;
|
|
27
|
+
if (decoded.length === 32) {
|
|
28
|
+
const hexStr = decoded.toString("ascii");
|
|
29
|
+
if (/^[0-9a-fA-F]{32}$/.test(hexStr)) {
|
|
30
|
+
return Buffer.from(hexStr, "hex");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return decoded.subarray(0, 16);
|
|
34
|
+
}
|
|
35
|
+
export async function downloadAndDecrypt(encryptQueryParam, aesKey, cdnBaseUrl, filekey) {
|
|
36
|
+
let url = `${cdnBaseUrl}/download?encrypted_query_param=${encodeURIComponent(encryptQueryParam)}`;
|
|
37
|
+
if (filekey) {
|
|
38
|
+
url += `&filekey=${encodeURIComponent(filekey)}`;
|
|
39
|
+
}
|
|
40
|
+
const res = await fetch(url);
|
|
41
|
+
if (!res.ok)
|
|
42
|
+
throw new Error(`CDN download failed: HTTP ${res.status}`);
|
|
43
|
+
const ciphertext = Buffer.from(await res.arrayBuffer());
|
|
44
|
+
return decryptAesEcb(ciphertext, aesKey);
|
|
45
|
+
}
|
|
46
|
+
/** Maximum retry attempts for CDN upload. */
|
|
47
|
+
const UPLOAD_MAX_RETRIES = 3;
|
|
48
|
+
export async function uploadToCdn(params) {
|
|
49
|
+
const encrypted = encryptAesEcb(params.buffer, params.aesKey);
|
|
50
|
+
const url = params.uploadUrl ?? `${params.cdnBaseUrl}/upload?encrypted_query_param=${encodeURIComponent(params.uploadParam)}&filekey=${encodeURIComponent(params.filekey)}`;
|
|
51
|
+
let downloadParam;
|
|
52
|
+
let lastError;
|
|
53
|
+
for (let attempt = 1; attempt <= UPLOAD_MAX_RETRIES; attempt++) {
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch(url, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: { "Content-Type": "application/octet-stream" },
|
|
58
|
+
body: new Uint8Array(encrypted),
|
|
59
|
+
});
|
|
60
|
+
if (res.status >= 400 && res.status < 500) {
|
|
61
|
+
const errMsg = res.headers.get("x-error-message") ?? (await res.text());
|
|
62
|
+
throw new Error(`CDN upload client error ${res.status}: ${errMsg}`);
|
|
63
|
+
}
|
|
64
|
+
if (res.status !== 200) {
|
|
65
|
+
const errMsg = res.headers.get("x-error-message") ?? `status ${res.status}`;
|
|
66
|
+
throw new Error(`CDN upload server error: ${errMsg}`);
|
|
67
|
+
}
|
|
68
|
+
downloadParam = res.headers.get("x-encrypted-param") ?? undefined;
|
|
69
|
+
if (!downloadParam) {
|
|
70
|
+
const body = await res.text();
|
|
71
|
+
throw new Error(`CDN upload: missing x-encrypted-param header. Body: ${body}`);
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
lastError = err;
|
|
77
|
+
if (err instanceof Error && err.message.includes("client error"))
|
|
78
|
+
throw err;
|
|
79
|
+
if (attempt < UPLOAD_MAX_RETRIES) {
|
|
80
|
+
// retry
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (!downloadParam) {
|
|
85
|
+
throw lastError instanceof Error
|
|
86
|
+
? lastError
|
|
87
|
+
: new Error(`CDN upload failed after ${UPLOAD_MAX_RETRIES} attempts`);
|
|
88
|
+
}
|
|
89
|
+
return downloadParam ?? "";
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.js","sourceRoot":"","sources":["../../../src/weixin/media.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeChat long-poll monitor loop.
|
|
3
|
+
* Polls getUpdates, dispatches messages via callback.
|
|
4
|
+
*/
|
|
5
|
+
import type { WeixinMessage } from "./types.js";
|
|
6
|
+
export interface MonitorOpts {
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
token?: string;
|
|
9
|
+
storageDir: string;
|
|
10
|
+
abortSignal?: AbortSignal;
|
|
11
|
+
longPollTimeoutMs?: number;
|
|
12
|
+
log: (msg: string) => void;
|
|
13
|
+
onMessage: (msg: WeixinMessage) => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function startMonitor(opts: MonitorOpts): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=monitor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monitor.d.ts","sourceRoot":"","sources":["../../../src/weixin/monitor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAkB,MAAM,YAAY,CAAC;AAQhE,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3B,SAAS,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,IAAI,CAAC;CACzC;AA8BD,wBAAsB,YAAY,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA+EnE"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WeChat long-poll monitor loop.
|
|
3
|
+
* Polls getUpdates, dispatches messages via callback.
|
|
4
|
+
*/
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { getUpdates } from "./api.js";
|
|
8
|
+
const DEFAULT_LONG_POLL_TIMEOUT_MS = 35_000;
|
|
9
|
+
const MAX_CONSECUTIVE_FAILURES = 3;
|
|
10
|
+
const BACKOFF_DELAY_MS = 30_000;
|
|
11
|
+
const RETRY_DELAY_MS = 2_000;
|
|
12
|
+
const SESSION_EXPIRED_ERRCODE = -14;
|
|
13
|
+
function getSyncBufPath(storageDir) {
|
|
14
|
+
return path.join(storageDir, "sync-buf.json");
|
|
15
|
+
}
|
|
16
|
+
function loadSyncBuf(storageDir) {
|
|
17
|
+
const p = getSyncBufPath(storageDir);
|
|
18
|
+
if (!fs.existsSync(p))
|
|
19
|
+
return "";
|
|
20
|
+
try {
|
|
21
|
+
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
22
|
+
return data.get_updates_buf ?? "";
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function saveSyncBuf(storageDir, buf) {
|
|
29
|
+
fs.mkdirSync(storageDir, { recursive: true });
|
|
30
|
+
fs.writeFileSync(getSyncBufPath(storageDir), JSON.stringify({ get_updates_buf: buf }), "utf-8");
|
|
31
|
+
}
|
|
32
|
+
function sleep(ms, signal) {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
if (signal?.aborted) {
|
|
35
|
+
reject(new Error("aborted"));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const t = setTimeout(resolve, ms);
|
|
39
|
+
signal?.addEventListener("abort", () => { clearTimeout(t); reject(new Error("aborted")); }, { once: true });
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export async function startMonitor(opts) {
|
|
43
|
+
const { baseUrl, token, storageDir, abortSignal, log, onMessage } = opts;
|
|
44
|
+
let getUpdatesBuf = loadSyncBuf(storageDir);
|
|
45
|
+
if (getUpdatesBuf) {
|
|
46
|
+
log(`Resuming from previous sync buf (${getUpdatesBuf.length} bytes)`);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
log("No previous sync buf, starting fresh");
|
|
50
|
+
}
|
|
51
|
+
let nextTimeoutMs = opts.longPollTimeoutMs ?? DEFAULT_LONG_POLL_TIMEOUT_MS;
|
|
52
|
+
let consecutiveFailures = 0;
|
|
53
|
+
while (!abortSignal?.aborted) {
|
|
54
|
+
try {
|
|
55
|
+
const resp = await getUpdates({
|
|
56
|
+
baseUrl,
|
|
57
|
+
token,
|
|
58
|
+
get_updates_buf: getUpdatesBuf,
|
|
59
|
+
timeoutMs: nextTimeoutMs,
|
|
60
|
+
});
|
|
61
|
+
if (resp.longpolling_timeout_ms != null && resp.longpolling_timeout_ms > 0) {
|
|
62
|
+
nextTimeoutMs = resp.longpolling_timeout_ms;
|
|
63
|
+
}
|
|
64
|
+
const isApiError = (resp.ret !== undefined && resp.ret !== 0) ||
|
|
65
|
+
(resp.errcode !== undefined && resp.errcode !== 0);
|
|
66
|
+
if (isApiError) {
|
|
67
|
+
const isSessionExpired = resp.errcode === SESSION_EXPIRED_ERRCODE || resp.ret === SESSION_EXPIRED_ERRCODE;
|
|
68
|
+
if (isSessionExpired) {
|
|
69
|
+
log(`Session expired (errcode ${SESSION_EXPIRED_ERRCODE}), pausing 1 hour...`);
|
|
70
|
+
consecutiveFailures = 0;
|
|
71
|
+
await sleep(60 * 60_000, abortSignal);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
consecutiveFailures++;
|
|
75
|
+
log(`getUpdates failed: ret=${resp.ret} errcode=${resp.errcode} errmsg=${resp.errmsg ?? ""} (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES})`);
|
|
76
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
77
|
+
log(`${MAX_CONSECUTIVE_FAILURES} consecutive failures, backing off ${BACKOFF_DELAY_MS / 1000}s`);
|
|
78
|
+
consecutiveFailures = 0;
|
|
79
|
+
await sleep(BACKOFF_DELAY_MS, abortSignal);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
await sleep(RETRY_DELAY_MS, abortSignal);
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
consecutiveFailures = 0;
|
|
87
|
+
if (resp.get_updates_buf != null && resp.get_updates_buf !== "") {
|
|
88
|
+
saveSyncBuf(storageDir, resp.get_updates_buf);
|
|
89
|
+
getUpdatesBuf = resp.get_updates_buf;
|
|
90
|
+
}
|
|
91
|
+
for (const msg of resp.msgs ?? []) {
|
|
92
|
+
onMessage(msg);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
if (abortSignal?.aborted)
|
|
97
|
+
return;
|
|
98
|
+
consecutiveFailures++;
|
|
99
|
+
log(`getUpdates error (${consecutiveFailures}/${MAX_CONSECUTIVE_FAILURES}): ${String(err)}`);
|
|
100
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
101
|
+
log(`${MAX_CONSECUTIVE_FAILURES} consecutive failures, backing off ${BACKOFF_DELAY_MS / 1000}s`);
|
|
102
|
+
consecutiveFailures = 0;
|
|
103
|
+
await sleep(BACKOFF_DELAY_MS, abortSignal);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
await sleep(RETRY_DELAY_MS, abortSignal);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=monitor.js.map
|