dingtalk-stream 2.1.4 → 2.1.6-beta.1
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 +11 -0
- package/dist/client.cjs +92 -17
- package/dist/client.d.ts +7 -1
- package/dist/client.mjs +92 -17
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
<p align="left">
|
|
2
|
+
<a target="_blank" href="https://github.com/open-dingtalk/dingtalk-stream-sdk-nodejs/actions/workflows/publish.yml">
|
|
3
|
+
<img src="https://img.shields.io/github/actions/workflow/status/open-dingtalk/dingtalk-stream-sdk-nodejs/publish.yml" />
|
|
4
|
+
</a>
|
|
5
|
+
|
|
6
|
+
<a target="_blank" href="https://www.npmjs.com/package/dingtalk-stream">
|
|
7
|
+
<img alt="NPM Version" src="https://img.shields.io/npm/v/dingtalk-stream">
|
|
8
|
+
</a>
|
|
9
|
+
|
|
10
|
+
</p>
|
|
11
|
+
|
|
1
12
|
钉钉支持 Stream 模式接入事件推送、机器人收消息以及卡片回调,该 SDK 实现了 Stream 模式。相比 Webhook 模式,Stream 模式可以更简单的接入各类事件和回调。
|
|
2
13
|
|
|
3
14
|
## 开发教程
|
package/dist/client.cjs
CHANGED
|
@@ -27,9 +27,13 @@ class DWClient extends EventEmitter {
|
|
|
27
27
|
registered = false;
|
|
28
28
|
reconnecting = false;
|
|
29
29
|
userDisconnect = false;
|
|
30
|
-
|
|
30
|
+
reconnectBaseInterval = 1e3;
|
|
31
|
+
reconnectMaxInterval = 6e4;
|
|
32
|
+
reconnectAttempts = 0;
|
|
31
33
|
heartbeat_interval = 8e3;
|
|
32
34
|
heartbeatIntervallId;
|
|
35
|
+
reconnectTimerId;
|
|
36
|
+
isConnecting = false;
|
|
33
37
|
sslopts = { rejectUnauthorized: true };
|
|
34
38
|
config;
|
|
35
39
|
socket;
|
|
@@ -128,13 +132,52 @@ class DWClient extends EventEmitter {
|
|
|
128
132
|
throw new Error("build: get endpoint failed");
|
|
129
133
|
}
|
|
130
134
|
}
|
|
135
|
+
cleanup() {
|
|
136
|
+
if (this.heartbeatIntervallId !== void 0) {
|
|
137
|
+
clearInterval(this.heartbeatIntervallId);
|
|
138
|
+
this.heartbeatIntervallId = void 0;
|
|
139
|
+
}
|
|
140
|
+
if (this.socket) {
|
|
141
|
+
this.socket.removeAllListeners();
|
|
142
|
+
if (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING) {
|
|
143
|
+
this.socket.terminate();
|
|
144
|
+
}
|
|
145
|
+
this.socket = void 0;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
scheduleReconnect() {
|
|
149
|
+
if (!this.config.autoReconnect || this.userDisconnect || this.isConnecting) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const delay = Math.min(
|
|
153
|
+
this.reconnectBaseInterval * Math.pow(2, this.reconnectAttempts) + Math.random() * 1e3,
|
|
154
|
+
this.reconnectMaxInterval
|
|
155
|
+
);
|
|
156
|
+
this.reconnecting = true;
|
|
157
|
+
this.printDebug("Reconnecting in " + (delay / 1e3).toFixed(1) + " seconds... (attempt " + (this.reconnectAttempts + 1) + ")");
|
|
158
|
+
if (this.reconnectTimerId) {
|
|
159
|
+
clearTimeout(this.reconnectTimerId);
|
|
160
|
+
}
|
|
161
|
+
this.reconnectTimerId = setTimeout(() => {
|
|
162
|
+
this.reconnectTimerId = void 0;
|
|
163
|
+
this.connect();
|
|
164
|
+
}, delay);
|
|
165
|
+
}
|
|
131
166
|
_connect() {
|
|
132
167
|
return new Promise((resolve, reject) => {
|
|
133
|
-
this.userDisconnect = false;
|
|
134
168
|
this.printDebug("Connecting to dingtalk websocket @ " + this.dw_url);
|
|
135
|
-
|
|
169
|
+
try {
|
|
170
|
+
this.socket = new WebSocket(this.dw_url, this.sslopts);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
this.printDebug("WebSocket constructor error");
|
|
173
|
+
console.warn("ERROR", err);
|
|
174
|
+
reject(err);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
let settled = false;
|
|
136
178
|
this.socket.on("open", () => {
|
|
137
179
|
this.connected = true;
|
|
180
|
+
this.reconnectAttempts = 0;
|
|
138
181
|
console.info("[" + (/* @__PURE__ */ new Date()).toISOString() + "] connect success");
|
|
139
182
|
if (this.config.keepAlive) {
|
|
140
183
|
this.isAlive = true;
|
|
@@ -150,6 +193,8 @@ class DWClient extends EventEmitter {
|
|
|
150
193
|
(_b = this.socket) == null ? void 0 : _b.ping("", true);
|
|
151
194
|
}, this.heartbeat_interval);
|
|
152
195
|
}
|
|
196
|
+
settled = true;
|
|
197
|
+
resolve();
|
|
153
198
|
});
|
|
154
199
|
this.socket.on("pong", () => {
|
|
155
200
|
this.heartbeat();
|
|
@@ -157,37 +202,67 @@ class DWClient extends EventEmitter {
|
|
|
157
202
|
this.socket.on("message", (data) => {
|
|
158
203
|
this.onDownStream(data);
|
|
159
204
|
});
|
|
160
|
-
this.socket.on("close", (
|
|
205
|
+
this.socket.on("close", () => {
|
|
161
206
|
this.printDebug("Socket closed");
|
|
162
207
|
this.connected = false;
|
|
163
208
|
this.registered = false;
|
|
164
|
-
if (this.
|
|
165
|
-
this.
|
|
166
|
-
this.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
209
|
+
if (this.heartbeatIntervallId !== void 0) {
|
|
210
|
+
clearInterval(this.heartbeatIntervallId);
|
|
211
|
+
this.heartbeatIntervallId = void 0;
|
|
212
|
+
}
|
|
213
|
+
if (settled) {
|
|
214
|
+
this.scheduleReconnect();
|
|
170
215
|
}
|
|
171
216
|
});
|
|
172
217
|
this.socket.on("error", (err) => {
|
|
218
|
+
var _a;
|
|
173
219
|
this.printDebug("SOCKET ERROR");
|
|
174
220
|
console.warn("ERROR", err);
|
|
221
|
+
(_a = this.socket) == null ? void 0 : _a.terminate();
|
|
222
|
+
if (!settled) {
|
|
223
|
+
settled = true;
|
|
224
|
+
reject(err);
|
|
225
|
+
}
|
|
175
226
|
});
|
|
176
|
-
resolve();
|
|
177
227
|
});
|
|
178
228
|
}
|
|
179
229
|
async connect() {
|
|
180
|
-
|
|
181
|
-
|
|
230
|
+
if (this.isConnecting) {
|
|
231
|
+
this.printDebug("connect() already in progress, skipping");
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
this.userDisconnect = false;
|
|
235
|
+
this.isConnecting = true;
|
|
236
|
+
try {
|
|
237
|
+
this.cleanup();
|
|
238
|
+
await this.getEndpoint();
|
|
239
|
+
if (this.userDisconnect)
|
|
240
|
+
return;
|
|
241
|
+
await this._connect();
|
|
242
|
+
} catch (err) {
|
|
243
|
+
this.printDebug("Connect failed: " + (err instanceof Error ? err.message : String(err)));
|
|
244
|
+
if (!this.userDisconnect) {
|
|
245
|
+
this.reconnectAttempts++;
|
|
246
|
+
this.isConnecting = false;
|
|
247
|
+
this.scheduleReconnect();
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
} finally {
|
|
251
|
+
this.isConnecting = false;
|
|
252
|
+
}
|
|
182
253
|
}
|
|
183
254
|
disconnect() {
|
|
184
|
-
var _a;
|
|
185
255
|
console.info("Disconnecting.");
|
|
186
256
|
this.userDisconnect = true;
|
|
187
|
-
if (this.
|
|
188
|
-
|
|
257
|
+
if (this.reconnectTimerId) {
|
|
258
|
+
clearTimeout(this.reconnectTimerId);
|
|
259
|
+
this.reconnectTimerId = void 0;
|
|
189
260
|
}
|
|
190
|
-
|
|
261
|
+
this.reconnecting = false;
|
|
262
|
+
this.reconnectAttempts = 0;
|
|
263
|
+
this.cleanup();
|
|
264
|
+
this.connected = false;
|
|
265
|
+
this.registered = false;
|
|
191
266
|
}
|
|
192
267
|
heartbeat() {
|
|
193
268
|
this.isAlive = true;
|
package/dist/client.d.ts
CHANGED
|
@@ -50,9 +50,13 @@ declare class DWClient extends EventEmitter {
|
|
|
50
50
|
registered: boolean;
|
|
51
51
|
reconnecting: boolean;
|
|
52
52
|
private userDisconnect;
|
|
53
|
-
private
|
|
53
|
+
private reconnectBaseInterval;
|
|
54
|
+
private reconnectMaxInterval;
|
|
55
|
+
private reconnectAttempts;
|
|
54
56
|
private heartbeat_interval;
|
|
55
57
|
private heartbeatIntervallId?;
|
|
58
|
+
private reconnectTimerId?;
|
|
59
|
+
private isConnecting;
|
|
56
60
|
private sslopts;
|
|
57
61
|
readonly config: DWClientConfig;
|
|
58
62
|
private socket?;
|
|
@@ -85,6 +89,8 @@ declare class DWClient extends EventEmitter {
|
|
|
85
89
|
registerCallbackListener(eventId: string, callback: (v: DWClientDownStream) => void): this;
|
|
86
90
|
getAccessToken(): Promise<any>;
|
|
87
91
|
getEndpoint(): Promise<this>;
|
|
92
|
+
private cleanup;
|
|
93
|
+
private scheduleReconnect;
|
|
88
94
|
_connect(): Promise<void>;
|
|
89
95
|
connect(): Promise<void>;
|
|
90
96
|
disconnect(): void;
|
package/dist/client.mjs
CHANGED
|
@@ -25,9 +25,13 @@ class DWClient extends EventEmitter {
|
|
|
25
25
|
registered = false;
|
|
26
26
|
reconnecting = false;
|
|
27
27
|
userDisconnect = false;
|
|
28
|
-
|
|
28
|
+
reconnectBaseInterval = 1e3;
|
|
29
|
+
reconnectMaxInterval = 6e4;
|
|
30
|
+
reconnectAttempts = 0;
|
|
29
31
|
heartbeat_interval = 8e3;
|
|
30
32
|
heartbeatIntervallId;
|
|
33
|
+
reconnectTimerId;
|
|
34
|
+
isConnecting = false;
|
|
31
35
|
sslopts = { rejectUnauthorized: true };
|
|
32
36
|
config;
|
|
33
37
|
socket;
|
|
@@ -126,13 +130,52 @@ class DWClient extends EventEmitter {
|
|
|
126
130
|
throw new Error("build: get endpoint failed");
|
|
127
131
|
}
|
|
128
132
|
}
|
|
133
|
+
cleanup() {
|
|
134
|
+
if (this.heartbeatIntervallId !== void 0) {
|
|
135
|
+
clearInterval(this.heartbeatIntervallId);
|
|
136
|
+
this.heartbeatIntervallId = void 0;
|
|
137
|
+
}
|
|
138
|
+
if (this.socket) {
|
|
139
|
+
this.socket.removeAllListeners();
|
|
140
|
+
if (this.socket.readyState === WebSocket.OPEN || this.socket.readyState === WebSocket.CONNECTING) {
|
|
141
|
+
this.socket.terminate();
|
|
142
|
+
}
|
|
143
|
+
this.socket = void 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
scheduleReconnect() {
|
|
147
|
+
if (!this.config.autoReconnect || this.userDisconnect || this.isConnecting) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const delay = Math.min(
|
|
151
|
+
this.reconnectBaseInterval * Math.pow(2, this.reconnectAttempts) + Math.random() * 1e3,
|
|
152
|
+
this.reconnectMaxInterval
|
|
153
|
+
);
|
|
154
|
+
this.reconnecting = true;
|
|
155
|
+
this.printDebug("Reconnecting in " + (delay / 1e3).toFixed(1) + " seconds... (attempt " + (this.reconnectAttempts + 1) + ")");
|
|
156
|
+
if (this.reconnectTimerId) {
|
|
157
|
+
clearTimeout(this.reconnectTimerId);
|
|
158
|
+
}
|
|
159
|
+
this.reconnectTimerId = setTimeout(() => {
|
|
160
|
+
this.reconnectTimerId = void 0;
|
|
161
|
+
this.connect();
|
|
162
|
+
}, delay);
|
|
163
|
+
}
|
|
129
164
|
_connect() {
|
|
130
165
|
return new Promise((resolve, reject) => {
|
|
131
|
-
this.userDisconnect = false;
|
|
132
166
|
this.printDebug("Connecting to dingtalk websocket @ " + this.dw_url);
|
|
133
|
-
|
|
167
|
+
try {
|
|
168
|
+
this.socket = new WebSocket(this.dw_url, this.sslopts);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
this.printDebug("WebSocket constructor error");
|
|
171
|
+
console.warn("ERROR", err);
|
|
172
|
+
reject(err);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
let settled = false;
|
|
134
176
|
this.socket.on("open", () => {
|
|
135
177
|
this.connected = true;
|
|
178
|
+
this.reconnectAttempts = 0;
|
|
136
179
|
console.info("[" + (/* @__PURE__ */ new Date()).toISOString() + "] connect success");
|
|
137
180
|
if (this.config.keepAlive) {
|
|
138
181
|
this.isAlive = true;
|
|
@@ -148,6 +191,8 @@ class DWClient extends EventEmitter {
|
|
|
148
191
|
(_b = this.socket) == null ? void 0 : _b.ping("", true);
|
|
149
192
|
}, this.heartbeat_interval);
|
|
150
193
|
}
|
|
194
|
+
settled = true;
|
|
195
|
+
resolve();
|
|
151
196
|
});
|
|
152
197
|
this.socket.on("pong", () => {
|
|
153
198
|
this.heartbeat();
|
|
@@ -155,37 +200,67 @@ class DWClient extends EventEmitter {
|
|
|
155
200
|
this.socket.on("message", (data) => {
|
|
156
201
|
this.onDownStream(data);
|
|
157
202
|
});
|
|
158
|
-
this.socket.on("close", (
|
|
203
|
+
this.socket.on("close", () => {
|
|
159
204
|
this.printDebug("Socket closed");
|
|
160
205
|
this.connected = false;
|
|
161
206
|
this.registered = false;
|
|
162
|
-
if (this.
|
|
163
|
-
this.
|
|
164
|
-
this.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
207
|
+
if (this.heartbeatIntervallId !== void 0) {
|
|
208
|
+
clearInterval(this.heartbeatIntervallId);
|
|
209
|
+
this.heartbeatIntervallId = void 0;
|
|
210
|
+
}
|
|
211
|
+
if (settled) {
|
|
212
|
+
this.scheduleReconnect();
|
|
168
213
|
}
|
|
169
214
|
});
|
|
170
215
|
this.socket.on("error", (err) => {
|
|
216
|
+
var _a;
|
|
171
217
|
this.printDebug("SOCKET ERROR");
|
|
172
218
|
console.warn("ERROR", err);
|
|
219
|
+
(_a = this.socket) == null ? void 0 : _a.terminate();
|
|
220
|
+
if (!settled) {
|
|
221
|
+
settled = true;
|
|
222
|
+
reject(err);
|
|
223
|
+
}
|
|
173
224
|
});
|
|
174
|
-
resolve();
|
|
175
225
|
});
|
|
176
226
|
}
|
|
177
227
|
async connect() {
|
|
178
|
-
|
|
179
|
-
|
|
228
|
+
if (this.isConnecting) {
|
|
229
|
+
this.printDebug("connect() already in progress, skipping");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
this.userDisconnect = false;
|
|
233
|
+
this.isConnecting = true;
|
|
234
|
+
try {
|
|
235
|
+
this.cleanup();
|
|
236
|
+
await this.getEndpoint();
|
|
237
|
+
if (this.userDisconnect)
|
|
238
|
+
return;
|
|
239
|
+
await this._connect();
|
|
240
|
+
} catch (err) {
|
|
241
|
+
this.printDebug("Connect failed: " + (err instanceof Error ? err.message : String(err)));
|
|
242
|
+
if (!this.userDisconnect) {
|
|
243
|
+
this.reconnectAttempts++;
|
|
244
|
+
this.isConnecting = false;
|
|
245
|
+
this.scheduleReconnect();
|
|
246
|
+
}
|
|
247
|
+
return;
|
|
248
|
+
} finally {
|
|
249
|
+
this.isConnecting = false;
|
|
250
|
+
}
|
|
180
251
|
}
|
|
181
252
|
disconnect() {
|
|
182
|
-
var _a;
|
|
183
253
|
console.info("Disconnecting.");
|
|
184
254
|
this.userDisconnect = true;
|
|
185
|
-
if (this.
|
|
186
|
-
|
|
255
|
+
if (this.reconnectTimerId) {
|
|
256
|
+
clearTimeout(this.reconnectTimerId);
|
|
257
|
+
this.reconnectTimerId = void 0;
|
|
187
258
|
}
|
|
188
|
-
|
|
259
|
+
this.reconnecting = false;
|
|
260
|
+
this.reconnectAttempts = 0;
|
|
261
|
+
this.cleanup();
|
|
262
|
+
this.connected = false;
|
|
263
|
+
this.registered = false;
|
|
189
264
|
}
|
|
190
265
|
heartbeat() {
|
|
191
266
|
this.isAlive = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dingtalk-stream",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "v2.1.6-beta.1",
|
|
4
4
|
"description": "Nodejs SDK for DingTalk Stream Mode API, Compared with the webhook mode, it is easier to access the DingTalk",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -37,7 +37,10 @@
|
|
|
37
37
|
"build": "rimraf dist && rollup -c",
|
|
38
38
|
"dev": "rollup -c --watch --watch.include 'src/**' -m inline",
|
|
39
39
|
"prepublishOnly": "pnpm build",
|
|
40
|
-
"typecheck": "tsc --noEmit"
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"test": "pnpm build && node test/reconnect-mock.mjs",
|
|
42
|
+
"test:prod": "pnpm build && node test/reconnect-prod.mjs",
|
|
43
|
+
"test:demo": "node test/reconnect-storm-demo.mjs"
|
|
41
44
|
},
|
|
42
45
|
"repository": {
|
|
43
46
|
"type": "git",
|
|
@@ -72,6 +75,7 @@
|
|
|
72
75
|
"@types/debug": "^4.1.8",
|
|
73
76
|
"@types/node": ">=16",
|
|
74
77
|
"@types/ws": "^8.5.5",
|
|
78
|
+
"nock": "^14.0.11",
|
|
75
79
|
"rimraf": "^5.0.1",
|
|
76
80
|
"rollup": "^3.28.0",
|
|
77
81
|
"rollup-plugin-dts": "^6.0.0",
|