dsh-connect 0.7.2 → 0.8.0
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/README.md +68 -32
- package/README.zh.md +67 -31
- package/client/client.js +387 -0
- package/client/client.js.map +7 -0
- package/client/settings-client.mjs +177 -0
- package/examples/minimal.config.json +16 -0
- package/lib/channels/dingtalk/adapter.d.ts +59 -0
- package/lib/channels/dingtalk/adapter.d.ts.map +1 -0
- package/lib/channels/dingtalk/adapter.js +123 -0
- package/lib/channels/dingtalk/adapter.js.map +1 -0
- package/lib/channels/dingtalk/i18n.d.ts +14 -0
- package/lib/channels/dingtalk/i18n.d.ts.map +1 -0
- package/lib/channels/dingtalk/i18n.js +16 -0
- package/lib/channels/dingtalk/i18n.js.map +1 -0
- package/lib/channels/dingtalk/index.d.ts +127 -0
- package/lib/channels/dingtalk/index.d.ts.map +1 -0
- package/lib/channels/dingtalk/index.js +144 -0
- package/lib/channels/dingtalk/index.js.map +1 -0
- package/lib/channels/dingtalk/message.d.ts +49 -0
- package/lib/channels/dingtalk/message.d.ts.map +1 -0
- package/lib/channels/dingtalk/message.js +43 -0
- package/lib/channels/dingtalk/message.js.map +1 -0
- package/lib/channels/dingtalk/stomp.d.ts +36 -0
- package/lib/channels/dingtalk/stomp.d.ts.map +1 -0
- package/lib/channels/dingtalk/stomp.js +107 -0
- package/lib/channels/dingtalk/stomp.js.map +1 -0
- package/lib/channels/dingtalk/stream.d.ts +53 -0
- package/lib/channels/dingtalk/stream.d.ts.map +1 -0
- package/lib/channels/dingtalk/stream.js +199 -0
- package/lib/channels/dingtalk/stream.js.map +1 -0
- package/lib/channels/dingtalk/webhook.d.ts +84 -0
- package/lib/channels/dingtalk/webhook.d.ts.map +1 -0
- package/lib/channels/dingtalk/webhook.js +143 -0
- package/lib/channels/dingtalk/webhook.js.map +1 -0
- package/lib/channels/feishu/adapter.d.ts +139 -0
- package/lib/channels/feishu/adapter.d.ts.map +1 -0
- package/lib/channels/feishu/adapter.js +677 -0
- package/lib/channels/feishu/adapter.js.map +1 -0
- package/lib/channels/feishu/i18n.d.ts +28 -0
- package/lib/channels/feishu/i18n.d.ts.map +1 -0
- package/lib/channels/feishu/i18n.js +40 -0
- package/lib/channels/feishu/i18n.js.map +1 -0
- package/lib/channels/feishu/index.d.ts +61 -0
- package/lib/channels/feishu/index.d.ts.map +1 -0
- package/lib/channels/feishu/index.js +77 -0
- package/lib/channels/feishu/index.js.map +1 -0
- package/lib/channels/feishu/onboard.d.ts +16 -0
- package/lib/channels/feishu/onboard.d.ts.map +1 -0
- package/lib/channels/feishu/onboard.js +86 -0
- package/lib/channels/feishu/onboard.js.map +1 -0
- package/lib/channels/telegram/adapter.d.ts +70 -0
- package/lib/channels/telegram/adapter.d.ts.map +1 -0
- package/lib/channels/telegram/adapter.js +454 -0
- package/lib/channels/telegram/adapter.js.map +1 -0
- package/lib/channels/telegram/client.d.ts +115 -0
- package/lib/channels/telegram/client.d.ts.map +1 -0
- package/lib/channels/telegram/client.js +198 -0
- package/lib/channels/telegram/client.js.map +1 -0
- package/lib/channels/telegram/i18n.d.ts +14 -0
- package/lib/channels/telegram/i18n.d.ts.map +1 -0
- package/lib/channels/telegram/i18n.js +18 -0
- package/lib/channels/telegram/i18n.js.map +1 -0
- package/lib/channels/telegram/index.d.ts +46 -0
- package/lib/channels/telegram/index.d.ts.map +1 -0
- package/lib/channels/telegram/index.js +54 -0
- package/lib/channels/telegram/index.js.map +1 -0
- package/lib/channels/web/adapter.d.ts +103 -0
- package/lib/channels/web/adapter.d.ts.map +1 -0
- package/lib/channels/web/adapter.js +161 -0
- package/lib/channels/web/adapter.js.map +1 -0
- package/lib/channels/web/index.d.ts +47 -0
- package/lib/channels/web/index.d.ts.map +1 -0
- package/lib/channels/web/index.js +59 -0
- package/lib/channels/web/index.js.map +1 -0
- package/lib/index.d.ts +68 -11
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +141 -8
- package/lib/index.js.map +1 -1
- package/lib/runner.d.ts +1 -2
- package/lib/runner.d.ts.map +1 -1
- package/lib/runner.js.map +1 -1
- package/lib/service.d.ts +1 -1
- package/lib/service.d.ts.map +1 -1
- package/lib/service.js +1 -1
- package/lib/service.js.map +1 -1
- package/lib/settings/channels.d.ts +51 -0
- package/lib/settings/channels.d.ts.map +1 -0
- package/lib/settings/channels.js +101 -0
- package/lib/settings/channels.js.map +1 -0
- package/lib/settings/credential-store.d.ts +39 -0
- package/lib/settings/credential-store.d.ts.map +1 -0
- package/lib/settings/credential-store.js +68 -0
- package/lib/settings/credential-store.js.map +1 -0
- package/lib/settings/index.d.ts +14 -0
- package/lib/settings/index.d.ts.map +1 -0
- package/lib/settings/index.js +14 -0
- package/lib/settings/index.js.map +1 -0
- package/lib/settings/rpc-client.d.ts +36 -0
- package/lib/settings/rpc-client.d.ts.map +1 -0
- package/lib/settings/rpc-client.js +45 -0
- package/lib/settings/rpc-client.js.map +1 -0
- package/lib/settings/settings-model.d.ts +107 -0
- package/lib/settings/settings-model.d.ts.map +1 -0
- package/lib/settings/settings-model.js +110 -0
- package/lib/settings/settings-model.js.map +1 -0
- package/lib/settings/settings-rpc.d.ts +62 -0
- package/lib/settings/settings-rpc.d.ts.map +1 -0
- package/lib/settings/settings-rpc.js +90 -0
- package/lib/settings/settings-rpc.js.map +1 -0
- package/lib/settings/settings-service.d.ts +27 -0
- package/lib/settings/settings-service.d.ts.map +1 -0
- package/lib/settings/settings-service.js +103 -0
- package/lib/settings/settings-service.js.map +1 -0
- package/package.json +48 -6
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DingTalk stream-mode gateway client: STOMP over WebSocket, zero third-party
|
|
3
|
+
* dependencies (Node ≥ 22 global `WebSocket`; older Node needs a polyfill —
|
|
4
|
+
* the constructor is looked up lazily on `globalThis` so the module loads
|
|
5
|
+
* fine on Node 20, it only fails at connect time).
|
|
6
|
+
*
|
|
7
|
+
* This file is the live network boundary: it is intentionally thin and every
|
|
8
|
+
* protocol detail it depends on lives in the pure modules (stomp.ts,
|
|
9
|
+
* message.ts) that carry the unit tests. Actual connectivity requires real
|
|
10
|
+
* app credentials and an outbound connection to the DingTalk gateway, so it
|
|
11
|
+
* cannot be exercised in CI here.
|
|
12
|
+
* @module dsh-connect/channels/dingtalk/stream
|
|
13
|
+
*/
|
|
14
|
+
import { decodeFrames, encodeFrame } from "./stomp.js";
|
|
15
|
+
import { buildConnectBody, INBOUND_DESTINATION, INBOUND_SUBSCRIPTION, REPLY_DESTINATION, } from "./message.js";
|
|
16
|
+
export const DEFAULT_STREAM_URL = "wss://api.dingtalk.com/connect";
|
|
17
|
+
/** STOMP heartbeat interval (client → gateway), ms. */
|
|
18
|
+
const HEARTBEAT_MS = 10_000;
|
|
19
|
+
const RECONNECT_BASE_MS = 1_000;
|
|
20
|
+
const RECONNECT_MAX_MS = 30_000;
|
|
21
|
+
function lazyWebSocketCtor() {
|
|
22
|
+
return globalThis.WebSocket;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* STOMP-over-WebSocket gateway client. Usage:
|
|
26
|
+
* ```ts
|
|
27
|
+
* const client = new DingtalkStreamClient({ clientId, clientSecret });
|
|
28
|
+
* client.onInbound((raw) => console.log(raw.conversationId, raw.text?.content));
|
|
29
|
+
* await client.connect();
|
|
30
|
+
* await client.sendReply(msgId, buildTextReplyBody(msgId, "hi"));
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export class DingtalkStreamClient {
|
|
34
|
+
clientId;
|
|
35
|
+
clientSecret;
|
|
36
|
+
url;
|
|
37
|
+
logger;
|
|
38
|
+
ws;
|
|
39
|
+
rest = "";
|
|
40
|
+
heartbeat;
|
|
41
|
+
reconnectTimer;
|
|
42
|
+
backoffMs = RECONNECT_BASE_MS;
|
|
43
|
+
closedByUs = false;
|
|
44
|
+
messageHandler;
|
|
45
|
+
constructor(options) {
|
|
46
|
+
this.clientId = options.clientId;
|
|
47
|
+
this.clientSecret = options.clientSecret;
|
|
48
|
+
this.url = options.url ?? DEFAULT_STREAM_URL;
|
|
49
|
+
this.logger = options.logger;
|
|
50
|
+
}
|
|
51
|
+
/** Register the inbound-message handler (called for every gateway push). */
|
|
52
|
+
onInbound(handler) {
|
|
53
|
+
this.messageHandler = handler;
|
|
54
|
+
}
|
|
55
|
+
/** Open the socket, perform the STOMP handshake and subscribe. Reconnect-safe. */
|
|
56
|
+
async connect() {
|
|
57
|
+
const Ctor = lazyWebSocketCtor();
|
|
58
|
+
if (Ctor === undefined) {
|
|
59
|
+
throw new Error("connect-dingtalk: WebSocket is not available on this runtime (Node ≥ 22 or a polyfill required)");
|
|
60
|
+
}
|
|
61
|
+
if (this.closedByUs)
|
|
62
|
+
return;
|
|
63
|
+
this.logger?.info?.(`connect-dingtalk: connecting to ${this.url}`);
|
|
64
|
+
const ws = new Ctor(this.url);
|
|
65
|
+
this.ws = ws;
|
|
66
|
+
await new Promise((resolve, reject) => {
|
|
67
|
+
const timer = setTimeout(() => {
|
|
68
|
+
cleanup();
|
|
69
|
+
reject(new Error("connect-dingtalk: WebSocket handshake timed out"));
|
|
70
|
+
}, 15_000);
|
|
71
|
+
const cleanup = () => {
|
|
72
|
+
clearTimeout(timer);
|
|
73
|
+
ws.onopen = null;
|
|
74
|
+
ws.onerror = null;
|
|
75
|
+
};
|
|
76
|
+
ws.onopen = () => {
|
|
77
|
+
cleanup();
|
|
78
|
+
this.onSocketOpen();
|
|
79
|
+
resolve();
|
|
80
|
+
};
|
|
81
|
+
ws.onerror = () => {
|
|
82
|
+
// onclose fires next; the close handler owns reconnect scheduling.
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
ws.onmessage = (ev) => this.onSocketMessage(String(ev.data ?? ""));
|
|
86
|
+
ws.onclose = (ev) => this.onSocketClose(ev.code, ev.reason);
|
|
87
|
+
}
|
|
88
|
+
/** Send a reply to an inbound message (SEND frame to the reply topic). */
|
|
89
|
+
async sendReply(msgId, body) {
|
|
90
|
+
const ws = this.ws;
|
|
91
|
+
if (ws === undefined || ws.readyState !== 1 /* OPEN */) {
|
|
92
|
+
throw new Error("connect-dingtalk: stream not connected");
|
|
93
|
+
}
|
|
94
|
+
ws.send(encodeFrame("SEND", { destination: REPLY_DESTINATION }, body));
|
|
95
|
+
}
|
|
96
|
+
/** Stop the client and cancel any scheduled reconnect. */
|
|
97
|
+
close() {
|
|
98
|
+
this.closedByUs = true;
|
|
99
|
+
if (this.reconnectTimer !== undefined) {
|
|
100
|
+
clearTimeout(this.reconnectTimer);
|
|
101
|
+
this.reconnectTimer = undefined;
|
|
102
|
+
}
|
|
103
|
+
this.stopHeartbeat();
|
|
104
|
+
this.ws?.close(1000, "bye");
|
|
105
|
+
this.ws = undefined;
|
|
106
|
+
}
|
|
107
|
+
onSocketOpen() {
|
|
108
|
+
this.logger?.info?.("connect-dingtalk: socket open, sending CONNECT");
|
|
109
|
+
this.sendFrame("CONNECT", {
|
|
110
|
+
"accept-version": "1.2",
|
|
111
|
+
host: "api.dingtalk.com",
|
|
112
|
+
"heart-beat": `${HEARTBEAT_MS},${HEARTBEAT_MS}`,
|
|
113
|
+
}, buildConnectBody(this.clientId, this.clientSecret));
|
|
114
|
+
}
|
|
115
|
+
onSocketMessage(data) {
|
|
116
|
+
const { frames, rest } = decodeFrames(this.rest + data);
|
|
117
|
+
this.rest = rest;
|
|
118
|
+
for (const frame of frames)
|
|
119
|
+
this.handleFrame(frame);
|
|
120
|
+
}
|
|
121
|
+
onSocketClose(code, reason) {
|
|
122
|
+
this.stopHeartbeat();
|
|
123
|
+
this.ws = undefined;
|
|
124
|
+
if (this.closedByUs)
|
|
125
|
+
return;
|
|
126
|
+
this.logger?.warn?.(`connect-dingtalk: socket closed (code=${String(code)} reason=${String(reason)}), reconnecting in ${this.backoffMs}ms`);
|
|
127
|
+
this.scheduleReconnect();
|
|
128
|
+
}
|
|
129
|
+
scheduleReconnect() {
|
|
130
|
+
if (this.closedByUs || this.reconnectTimer !== undefined)
|
|
131
|
+
return;
|
|
132
|
+
const delay = this.backoffMs;
|
|
133
|
+
this.backoffMs = Math.min(this.backoffMs * 2, RECONNECT_MAX_MS);
|
|
134
|
+
this.reconnectTimer = setTimeout(() => {
|
|
135
|
+
this.reconnectTimer = undefined;
|
|
136
|
+
void this.connect().catch((error) => {
|
|
137
|
+
this.logger?.error?.(`connect-dingtalk: reconnect attempt failed: ${String(error)}`);
|
|
138
|
+
this.scheduleReconnect();
|
|
139
|
+
});
|
|
140
|
+
}, delay);
|
|
141
|
+
}
|
|
142
|
+
handleFrame(frame) {
|
|
143
|
+
switch (frame.command) {
|
|
144
|
+
case "CONNECTED": {
|
|
145
|
+
this.backoffMs = RECONNECT_BASE_MS;
|
|
146
|
+
this.logger?.info?.("connect-dingtalk: CONNECTED, subscribing");
|
|
147
|
+
this.sendFrame("SUBSCRIBE", { id: INBOUND_SUBSCRIPTION, destination: INBOUND_DESTINATION });
|
|
148
|
+
this.startHeartbeat();
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case "MESSAGE": {
|
|
152
|
+
if (this.messageHandler === undefined)
|
|
153
|
+
break;
|
|
154
|
+
try {
|
|
155
|
+
const raw = JSON.parse(frame.body);
|
|
156
|
+
void Promise.resolve(this.messageHandler(raw)).catch((error) => {
|
|
157
|
+
this.logger?.warn?.(`connect-dingtalk: inbound handler failed: ${String(error)}`);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
this.logger?.warn?.(`connect-dingtalk: malformed gateway payload: ${String(error)}`);
|
|
162
|
+
}
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case "ERROR": {
|
|
166
|
+
this.logger?.error?.(`connect-dingtalk: gateway ERROR: ${frame.body || frame.headers.message || "no detail"}`);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case "RECEIPT": {
|
|
170
|
+
this.logger?.info?.("connect-dingtalk: gateway receipt received");
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
default:
|
|
174
|
+
this.logger?.warn?.(`connect-dingtalk: unhandled STOMP frame ${frame.command}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
sendFrame(command, headers, body = "") {
|
|
178
|
+
const ws = this.ws;
|
|
179
|
+
if (ws === undefined || ws.readyState !== 1)
|
|
180
|
+
return;
|
|
181
|
+
ws.send(encodeFrame(command, headers, body));
|
|
182
|
+
}
|
|
183
|
+
startHeartbeat() {
|
|
184
|
+
this.stopHeartbeat();
|
|
185
|
+
this.heartbeat = setInterval(() => {
|
|
186
|
+
const ws = this.ws;
|
|
187
|
+
if (ws !== undefined && ws.readyState === 1)
|
|
188
|
+
ws.send("\n");
|
|
189
|
+
}, HEARTBEAT_MS);
|
|
190
|
+
this.heartbeat.unref?.();
|
|
191
|
+
}
|
|
192
|
+
stopHeartbeat() {
|
|
193
|
+
if (this.heartbeat !== undefined) {
|
|
194
|
+
clearInterval(this.heartbeat);
|
|
195
|
+
this.heartbeat = undefined;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
//# sourceMappingURL=stream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.js","sourceRoot":"","sources":["../../../src/channels/dingtalk/stream.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** DingTalk webhook configuration. */
|
|
2
|
+
export interface DingtalkWebhookConfig {
|
|
3
|
+
/** Full webhook URL, e.g. `https://oapi.dingtalk.com/robot/send?access_token=xxx`. */
|
|
4
|
+
webhookUrl: string;
|
|
5
|
+
/** Optional signing secret (`SEC...`) — when set, requests are signed. */
|
|
6
|
+
secret?: string;
|
|
7
|
+
/** HTTP timeout in ms (default 10s). */
|
|
8
|
+
timeoutMs?: number;
|
|
9
|
+
/** Backoff delays between retries for transient network errors (default 1s/2s/4s). */
|
|
10
|
+
retryDelaysMs?: readonly number[];
|
|
11
|
+
/** Extra delay for the 130101 frequency-limit retry (default 10s). */
|
|
12
|
+
rateLimitDelayMs?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Body shape accepted by the DingTalk robot API. */
|
|
15
|
+
export type DingtalkBody = {
|
|
16
|
+
msgtype: "text";
|
|
17
|
+
text: {
|
|
18
|
+
content: string;
|
|
19
|
+
};
|
|
20
|
+
at?: {
|
|
21
|
+
atMobiles?: string[];
|
|
22
|
+
atUserIds?: string[];
|
|
23
|
+
isAtAll?: boolean;
|
|
24
|
+
};
|
|
25
|
+
} | {
|
|
26
|
+
msgtype: "markdown";
|
|
27
|
+
markdown: {
|
|
28
|
+
title: string;
|
|
29
|
+
text: string;
|
|
30
|
+
};
|
|
31
|
+
at?: {
|
|
32
|
+
atMobiles?: string[];
|
|
33
|
+
atUserIds?: string[];
|
|
34
|
+
isAtAll?: boolean;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
/** Response of the DingTalk robot API. */
|
|
38
|
+
export interface DingtalkResponse {
|
|
39
|
+
errcode: number;
|
|
40
|
+
errmsg: string;
|
|
41
|
+
}
|
|
42
|
+
/** How to address people in a DingTalk group. */
|
|
43
|
+
export interface DingtalkAt {
|
|
44
|
+
/** Phone numbers — required by DingTalk for `atMobiles` (robots can't resolve names). */
|
|
45
|
+
mobiles?: readonly string[];
|
|
46
|
+
/** User ids (unionid/`userId` when known). */
|
|
47
|
+
userIds?: readonly string[];
|
|
48
|
+
/** @everyone. */
|
|
49
|
+
all?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** DingTalk caps markdown body text at 20000 chars. */
|
|
52
|
+
export declare const MARKDOWN_LIMIT = 20000;
|
|
53
|
+
/**
|
|
54
|
+
* Compute the DingTalk webhook signature. The SDK expects
|
|
55
|
+
* `timestamp + "\n" + secret` signed with HMAC-SHA256, base64-encoded, then
|
|
56
|
+
* URL-encoded as the `sign` query param.
|
|
57
|
+
*/
|
|
58
|
+
export declare function signDingtalk(secret: string, timestampMs: number): string;
|
|
59
|
+
/**
|
|
60
|
+
* Verify an incoming signature against a secret (used by webhook receivers, if
|
|
61
|
+
* ever needed). Both sides are compared after URL-decoding, so a sender that
|
|
62
|
+
* encoded `+` as `%2B` (or vice versa) still verifies, and the timestamp must
|
|
63
|
+
* be fresh (default: ±5 minutes) to be replay-resistant.
|
|
64
|
+
*/
|
|
65
|
+
export declare function verifyDingtalkSignature(secret: string, timestampMs: string, sign: string, nowMs?: number, maxAgeMs?: number): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Minimal DingTalk group-robot webhook client built on the global `fetch`
|
|
68
|
+
* (Node ≥ 18 / ≥ 20 — no third-party HTTP dependency). Transient failures
|
|
69
|
+
* (network errors, the 20/min frequency limit) are retried with backoff.
|
|
70
|
+
*/
|
|
71
|
+
export declare class DingtalkWebhook {
|
|
72
|
+
readonly config: DingtalkWebhookConfig;
|
|
73
|
+
constructor(config: DingtalkWebhookConfig);
|
|
74
|
+
/**
|
|
75
|
+
* Send a markdown message. `title` is shown as the message's card title in
|
|
76
|
+
* DingTalk; `text` is the markdown body (truncated to 20000 chars, the API
|
|
77
|
+
* limit). Optionally @-mention people.
|
|
78
|
+
*/
|
|
79
|
+
sendMarkdown(title: string, text: string, at?: DingtalkAt): Promise<DingtalkResponse>;
|
|
80
|
+
/** Send a plain-text message (content shown verbatim, `\n` newlines allowed). */
|
|
81
|
+
sendText(content: string, at?: DingtalkAt): Promise<DingtalkResponse>;
|
|
82
|
+
private send;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=webhook.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.d.ts","sourceRoot":"","sources":["../../../src/channels/dingtalk/webhook.ts"],"names":[],"mappings":"AAYA,sCAAsC;AACtC,MAAM,WAAW,qBAAqB;IACpC,sFAAsF;IACtF,UAAU,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sFAAsF;IACtF,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qDAAqD;AACrD,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1B,EAAE,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACxE,GAAG;IACF,OAAO,EAAE,UAAU,CAAC;IACpB,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,EAAE,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CACxE,CAAC;AAEF,0CAA0C;AAC1C,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,iDAAiD;AACjD,MAAM,WAAW,UAAU;IACzB,yFAAyF;IACzF,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5B,iBAAiB;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,uDAAuD;AACvD,eAAO,MAAM,cAAc,QAAS,CAAC;AAIrC;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CAIxE;AAUD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,EACZ,KAAK,GAAE,MAAmB,EAC1B,QAAQ,GAAE,MAAmB,GAC5B,OAAO,CAOT;AA2BD;;;;GAIG;AACH,qBAAa,eAAe;IAC1B,QAAQ,CAAC,MAAM,EAAE,qBAAqB,CAAC;gBAE3B,MAAM,EAAE,qBAAqB;IAOzC;;;;OAIG;IACG,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAK3F,iFAAiF;IAC3E,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,CAAC;YAI7D,IAAI;CAiCnB"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DingTalk group-robot webhook transport. A webhook URL is created from a
|
|
3
|
+
* group's "custom robot" (群自定义机器人): the group owner adds a robot, gets
|
|
4
|
+
* an `access_token`, and optionally enables a signing secret. This transport
|
|
5
|
+
* only *pushes* — DingTalk custom webhooks cannot receive messages, so this
|
|
6
|
+
* package is a one-way notice channel (progress / results / alerts), not a
|
|
7
|
+
* bidirectional conversation adapter like the Feishu one.
|
|
8
|
+
*
|
|
9
|
+
* @module dsh-connect/channels/dingtalk/webhook
|
|
10
|
+
*/
|
|
11
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
12
|
+
/** DingTalk caps markdown body text at 20000 chars. */
|
|
13
|
+
export const MARKDOWN_LIMIT = 20_000;
|
|
14
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
15
|
+
/**
|
|
16
|
+
* Compute the DingTalk webhook signature. The SDK expects
|
|
17
|
+
* `timestamp + "\n" + secret` signed with HMAC-SHA256, base64-encoded, then
|
|
18
|
+
* URL-encoded as the `sign` query param.
|
|
19
|
+
*/
|
|
20
|
+
export function signDingtalk(secret, timestampMs) {
|
|
21
|
+
const stringToSign = `${timestampMs}\n${secret}`;
|
|
22
|
+
const mac = createHmac("sha256", secret).update(stringToSign, "utf8").digest("base64");
|
|
23
|
+
return encodeURIComponent(mac);
|
|
24
|
+
}
|
|
25
|
+
function safeDecode(value) {
|
|
26
|
+
try {
|
|
27
|
+
return decodeURIComponent(value);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return value; // not URL-encoded — compare as-is
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Verify an incoming signature against a secret (used by webhook receivers, if
|
|
35
|
+
* ever needed). Both sides are compared after URL-decoding, so a sender that
|
|
36
|
+
* encoded `+` as `%2B` (or vice versa) still verifies, and the timestamp must
|
|
37
|
+
* be fresh (default: ±5 minutes) to be replay-resistant.
|
|
38
|
+
*/
|
|
39
|
+
export function verifyDingtalkSignature(secret, timestampMs, sign, nowMs = Date.now(), maxAgeMs = 5 * 60_000) {
|
|
40
|
+
const ts = Number(timestampMs);
|
|
41
|
+
if (!Number.isFinite(ts) || Math.abs(nowMs - ts) > maxAgeMs)
|
|
42
|
+
return false;
|
|
43
|
+
const expected = signDingtalk(secret, ts);
|
|
44
|
+
const a = Buffer.from(safeDecode(sign));
|
|
45
|
+
const b = Buffer.from(safeDecode(expected));
|
|
46
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
47
|
+
}
|
|
48
|
+
/** Replace non-url-safe chars in a DingTalk webhook URL's query (idempotent-safe). */
|
|
49
|
+
function withSignature(url, secret, timestampMs) {
|
|
50
|
+
const sign = signDingtalk(secret, timestampMs);
|
|
51
|
+
const sep = url.includes("?") ? "&" : "?";
|
|
52
|
+
return `${url}${sep}timestamp=${timestampMs}&sign=${sign}`;
|
|
53
|
+
}
|
|
54
|
+
/** Errors that are safe to retry: transient network issues and the frequency limit. */
|
|
55
|
+
function isRetryable(error) {
|
|
56
|
+
const errcode = error.errcode;
|
|
57
|
+
if (errcode === 130101)
|
|
58
|
+
return true; // 发送频率限制 (20/min per robot)
|
|
59
|
+
if (error instanceof TypeError)
|
|
60
|
+
return true; // fetch network failure
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
function delayFor(config, error, attempt) {
|
|
64
|
+
const errcode = error.errcode;
|
|
65
|
+
if (errcode === 130101)
|
|
66
|
+
return config.rateLimitDelayMs ?? 10_000; // rate limit: wait for the window to pass
|
|
67
|
+
const backoff = config.retryDelaysMs;
|
|
68
|
+
if (backoff !== undefined && backoff.length > 0)
|
|
69
|
+
return backoff[Math.min(attempt, backoff.length - 1)] ?? 1_000;
|
|
70
|
+
return 1_000 * 2 ** attempt; // 1s, 2s, 4s…
|
|
71
|
+
}
|
|
72
|
+
const MAX_ATTEMPTS = 3;
|
|
73
|
+
/**
|
|
74
|
+
* Minimal DingTalk group-robot webhook client built on the global `fetch`
|
|
75
|
+
* (Node ≥ 18 / ≥ 20 — no third-party HTTP dependency). Transient failures
|
|
76
|
+
* (network errors, the 20/min frequency limit) are retried with backoff.
|
|
77
|
+
*/
|
|
78
|
+
export class DingtalkWebhook {
|
|
79
|
+
config;
|
|
80
|
+
constructor(config) {
|
|
81
|
+
if (!config.webhookUrl || !config.webhookUrl.startsWith("https://")) {
|
|
82
|
+
throw new Error("connect-dingtalk: webhookUrl must be a https:// DingTalk robot URL");
|
|
83
|
+
}
|
|
84
|
+
this.config = config;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Send a markdown message. `title` is shown as the message's card title in
|
|
88
|
+
* DingTalk; `text` is the markdown body (truncated to 20000 chars, the API
|
|
89
|
+
* limit). Optionally @-mention people.
|
|
90
|
+
*/
|
|
91
|
+
async sendMarkdown(title, text, at) {
|
|
92
|
+
const body = text.length > MARKDOWN_LIMIT ? `${text.slice(0, MARKDOWN_LIMIT)}\n\n…(已截断)` : text;
|
|
93
|
+
return this.send({ msgtype: "markdown", markdown: { title, text: body }, at: atBody(at) });
|
|
94
|
+
}
|
|
95
|
+
/** Send a plain-text message (content shown verbatim, `\n` newlines allowed). */
|
|
96
|
+
async sendText(content, at) {
|
|
97
|
+
return this.send({ msgtype: "text", text: { content }, at: atBody(at) });
|
|
98
|
+
}
|
|
99
|
+
async send(body, attempt = 0) {
|
|
100
|
+
const { webhookUrl, secret, timeoutMs = 10_000 } = this.config;
|
|
101
|
+
const url = secret ? withSignature(webhookUrl, secret, Date.now()) : webhookUrl;
|
|
102
|
+
const controller = new AbortController();
|
|
103
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetch(url, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: { "Content-Type": "application/json" },
|
|
108
|
+
body: JSON.stringify(body),
|
|
109
|
+
signal: controller.signal,
|
|
110
|
+
});
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
throw new Error(`connect-dingtalk: HTTP ${response.status} ${response.statusText}`);
|
|
113
|
+
}
|
|
114
|
+
const parsed = (await response.json());
|
|
115
|
+
if (parsed.errcode !== 0) {
|
|
116
|
+
const error = new Error(`connect-dingtalk: DingTalk error ${parsed.errcode}: ${parsed.errmsg}`);
|
|
117
|
+
error.errcode = parsed.errcode;
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
return parsed;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (isRetryable(error) && attempt < MAX_ATTEMPTS) {
|
|
124
|
+
await sleep(delayFor(this.config, error, attempt));
|
|
125
|
+
return this.send(body, attempt + 1);
|
|
126
|
+
}
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function atBody(at) {
|
|
135
|
+
if (at === undefined)
|
|
136
|
+
return undefined;
|
|
137
|
+
return {
|
|
138
|
+
...(at.mobiles !== undefined && at.mobiles.length > 0 ? { atMobiles: [...at.mobiles] } : {}),
|
|
139
|
+
...(at.userIds !== undefined && at.userIds.length > 0 ? { atUserIds: [...at.userIds] } : {}),
|
|
140
|
+
...(at.all === true ? { isAtAll: true } : {}),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=webhook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../../src/channels/dingtalk/webhook.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { ChannelAdapter, ChoiceOption, ChoicePrompt, ChoiceResult, InboundMessage, OutboundTarget, SummaryCard } from "../../types.js";
|
|
2
|
+
import type { Language } from "../../i18n.js";
|
|
3
|
+
/** Pad every label with trailing spaces up to the same display width. */
|
|
4
|
+
export declare function padLabels(options: readonly ChoiceOption[]): ChoiceOption[];
|
|
5
|
+
/**
|
|
6
|
+
* Render options as an equal-width button grid: 2 buttons per row via
|
|
7
|
+
* `column_set` (two weighted columns), padding the last row with empty
|
|
8
|
+
* columns so every row is a uniform 2-cell layout. Labels are padded to the
|
|
9
|
+
* same display width so the buttons themselves render uniformly. Destructive
|
|
10
|
+
* actions (labels starting with ❌) render as red danger buttons.
|
|
11
|
+
*/
|
|
12
|
+
export declare function buildButtonGrid(options: readonly ChoiceOption[], columnsPerRow?: number): unknown[];
|
|
13
|
+
/**
|
|
14
|
+
* Render options as a single-select dropdown (`select_static`) inside an
|
|
15
|
+
* `action` container. Selecting an entry fires the card action with
|
|
16
|
+
* `action.tag === "select_static"` and the picked option id in `action.option`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function buildSelectMenu(options: readonly ChoiceOption[], placeholder?: string, initial?: string): unknown[];
|
|
19
|
+
/**
|
|
20
|
+
* Assemble the card elements for a choice prompt. When `sections` is given,
|
|
21
|
+
* the options are split into titled groups — each with a bold section caption
|
|
22
|
+
* and a divider before it — instead of one flat grid. Options not listed in
|
|
23
|
+
* any section (typically the trailing exit/back buttons) are rendered after
|
|
24
|
+
* the sections, separated by a divider.
|
|
25
|
+
*
|
|
26
|
+
* @param prompt - The choice prompt to render
|
|
27
|
+
* @param defaultColumns - Default number of columns per row (default: 2)
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildChoiceElements(prompt: ChoicePrompt, defaultColumns?: number): unknown[];
|
|
30
|
+
/**
|
|
31
|
+
* Encode a chatKey with an optional thread id. With `threadIsolation` on, a
|
|
32
|
+
* group message inside a thread gets `chatId:thread=<rootId>` as its chatKey,
|
|
33
|
+
* so each thread binds its own DSH session while outbound sends still target
|
|
34
|
+
* the base chat id (replies land in the thread via replyRef).
|
|
35
|
+
*/
|
|
36
|
+
export declare function encodeChatKey(chatId: string, threadId?: string): string;
|
|
37
|
+
/** Decode a possibly thread-scoped chatKey back to its base chat id. */
|
|
38
|
+
export declare function decodeChatKey(chatKey: string): {
|
|
39
|
+
chatId: string;
|
|
40
|
+
threadId?: string;
|
|
41
|
+
};
|
|
42
|
+
/** Classify a local file name for the Feishu upload API (image vs stream file). */
|
|
43
|
+
export declare function classifyFeishuFile(filename: string): "image" | "file";
|
|
44
|
+
/** Strip path separators / control chars from a downloaded file's name. */
|
|
45
|
+
export declare function sanitizeFileName(name: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Extract a human-readable error detail. Feishu business errors (missing
|
|
48
|
+
* permission, invalid resource, …) arrive as HTTP 200/400 with the real
|
|
49
|
+
* `{code, msg}` in the response body; prefer that over the axios fallback
|
|
50
|
+
* message like "Request failed with status code 400" that hides the cause.
|
|
51
|
+
*/
|
|
52
|
+
export declare function extractErrorDetail(error: unknown): string;
|
|
53
|
+
export interface FeishuConfig {
|
|
54
|
+
appId?: string;
|
|
55
|
+
appSecret?: string;
|
|
56
|
+
transport?: "websocket" | "webhook";
|
|
57
|
+
verificationToken?: string;
|
|
58
|
+
encryptKey?: string;
|
|
59
|
+
/** HTTP port for `transport: "webhook"` (default 9000). */
|
|
60
|
+
webhookPort?: number;
|
|
61
|
+
/** HTTP path the Feishu event callback posts to (default "/"). */
|
|
62
|
+
webhookPath?: string;
|
|
63
|
+
/** Group messages must @-mention the bot (default true). */
|
|
64
|
+
requireMention?: boolean;
|
|
65
|
+
/** Single-chat policy (SDK values): open / allowlist / pair / disabled. */
|
|
66
|
+
dmMode?: "open" | "allowlist" | "pair" | "disabled";
|
|
67
|
+
/** Bind one DSH session per group thread instead of one per chat (default false). */
|
|
68
|
+
threadIsolation?: boolean;
|
|
69
|
+
/** User-facing message language: `zh` (default) or `en`. */
|
|
70
|
+
language?: Language;
|
|
71
|
+
}
|
|
72
|
+
export declare class FeishuAdapter implements ChannelAdapter {
|
|
73
|
+
private readonly logger?;
|
|
74
|
+
private readonly isChatAllowed?;
|
|
75
|
+
readonly id = "feishu";
|
|
76
|
+
private readonly channel;
|
|
77
|
+
private readonly pendingChoices;
|
|
78
|
+
/** Cards whose stale-button tap was already noticed (dedupe, self-clearing). */
|
|
79
|
+
private readonly staleNoticed;
|
|
80
|
+
private readonly staleTimers;
|
|
81
|
+
private readonly t;
|
|
82
|
+
private readonly transport;
|
|
83
|
+
private readonly webhookPort;
|
|
84
|
+
private readonly webhookPath;
|
|
85
|
+
private readonly threadIsolation;
|
|
86
|
+
private server?;
|
|
87
|
+
private handler?;
|
|
88
|
+
constructor(config: FeishuConfig, logger?: {
|
|
89
|
+
info?: (...args: unknown[]) => void;
|
|
90
|
+
warn?: (...args: unknown[]) => void;
|
|
91
|
+
error?: (...args: unknown[]) => void;
|
|
92
|
+
} | undefined, isChatAllowed?: ((channel: string, chatKey: string, senderKey: string) => boolean) | undefined);
|
|
93
|
+
onInbound(handler: (msg: InboundMessage) => void | Promise<void>): void;
|
|
94
|
+
/** Resource types downloadable via `im.v1.messageResource.get`. Stickers are not supported by the Feishu API. */
|
|
95
|
+
private static readonly DOWNLOADABLE_TYPES;
|
|
96
|
+
/** Download attached images / files into local temp dirs; return their paths (and any failures). */
|
|
97
|
+
private downloadResources;
|
|
98
|
+
/**
|
|
99
|
+
* Download a resource inside a user message.
|
|
100
|
+
*
|
|
101
|
+
* Note: do NOT use the SDK's `downloadResource(fileKey, type)` — it calls
|
|
102
|
+
* `im.v1.image.get` / `im.v1.file.get` (download image / download file), and
|
|
103
|
+
* per the Feishu docs those endpoints **can only download resources uploaded
|
|
104
|
+
* by the bot itself**. Resources inside user-sent messages must be fetched
|
|
105
|
+
* with "get resource file from message" `im.v1.messageResource.get` (with
|
|
106
|
+
* message_id + type=image/file/audio/video), otherwise it returns HTTP 400.
|
|
107
|
+
*/
|
|
108
|
+
private downloadMessageResource;
|
|
109
|
+
/**
|
|
110
|
+
* Housekeeping for the per-channel temp dirs: remove downloads older than
|
|
111
|
+
* 24h so a busy bot doesn't fill the OS temp partition. Best-effort — a
|
|
112
|
+
* failure to clean is logged, never fatal.
|
|
113
|
+
*/
|
|
114
|
+
private cleanupTempDirs;
|
|
115
|
+
start(): Promise<void>;
|
|
116
|
+
stop(): Promise<void>;
|
|
117
|
+
/** Base chat id for outbound sends (strips any `:thread=` suffix). */
|
|
118
|
+
private chatIdOf;
|
|
119
|
+
sendText(target: OutboundTarget, text: string): Promise<void>;
|
|
120
|
+
sendCard(target: OutboundTarget, card: SummaryCard): Promise<void>;
|
|
121
|
+
streamText(target: OutboundTarget, chunks: AsyncIterable<string>): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Deliver a local file to the chat. The SDK uploads the source itself:
|
|
124
|
+
* images are sent inline, everything else as an attachment.
|
|
125
|
+
*/
|
|
126
|
+
sendFile(target: OutboundTarget, filePath: string, options?: {
|
|
127
|
+
filename?: string;
|
|
128
|
+
}): Promise<void>;
|
|
129
|
+
/**
|
|
130
|
+
* Build the send options shared by text/markdown deliveries: an optional
|
|
131
|
+
* reply target and @-mentions (SDK renders `<at user_id=…>` prefixes for
|
|
132
|
+
* both text and post messages, so group completion cards can nudge the
|
|
133
|
+
* requester).
|
|
134
|
+
*/
|
|
135
|
+
private sendOpts;
|
|
136
|
+
promptChoice(target: OutboundTarget, prompt: ChoicePrompt, updateMessageId?: string): Promise<ChoiceResult>;
|
|
137
|
+
closeMenu(messageId: string, summary: string): Promise<void>;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../src/channels/feishu/adapter.ts"],"names":[],"mappings":"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"}
|