dingtalk-stream 2.1.5 → 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/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
- reconnectInterval = 1e3;
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,9 +132,39 @@ 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 {
136
170
  this.socket = new WebSocket(this.dw_url, this.sslopts);
@@ -140,8 +174,10 @@ class DWClient extends EventEmitter {
140
174
  reject(err);
141
175
  return;
142
176
  }
177
+ let settled = false;
143
178
  this.socket.on("open", () => {
144
179
  this.connected = true;
180
+ this.reconnectAttempts = 0;
145
181
  console.info("[" + (/* @__PURE__ */ new Date()).toISOString() + "] connect success");
146
182
  if (this.config.keepAlive) {
147
183
  this.isAlive = true;
@@ -157,6 +193,7 @@ class DWClient extends EventEmitter {
157
193
  (_b = this.socket) == null ? void 0 : _b.ping("", true);
158
194
  }, this.heartbeat_interval);
159
195
  }
196
+ settled = true;
160
197
  resolve();
161
198
  });
162
199
  this.socket.on("pong", () => {
@@ -165,16 +202,16 @@ class DWClient extends EventEmitter {
165
202
  this.socket.on("message", (data) => {
166
203
  this.onDownStream(data);
167
204
  });
168
- this.socket.on("close", (err) => {
205
+ this.socket.on("close", () => {
169
206
  this.printDebug("Socket closed");
170
207
  this.connected = false;
171
208
  this.registered = false;
172
- if (this.config.autoReconnect && !this.userDisconnect) {
173
- this.reconnecting = true;
174
- this.printDebug(
175
- "Reconnecting in " + this.reconnectInterval / 1e3 + " seconds..."
176
- );
177
- setTimeout(this.connect.bind(this), this.reconnectInterval);
209
+ if (this.heartbeatIntervallId !== void 0) {
210
+ clearInterval(this.heartbeatIntervallId);
211
+ this.heartbeatIntervallId = void 0;
212
+ }
213
+ if (settled) {
214
+ this.scheduleReconnect();
178
215
  }
179
216
  });
180
217
  this.socket.on("error", (err) => {
@@ -182,31 +219,50 @@ class DWClient extends EventEmitter {
182
219
  this.printDebug("SOCKET ERROR");
183
220
  console.warn("ERROR", err);
184
221
  (_a = this.socket) == null ? void 0 : _a.terminate();
185
- reject(err);
222
+ if (!settled) {
223
+ settled = true;
224
+ reject(err);
225
+ }
186
226
  });
187
227
  });
188
228
  }
189
229
  async connect() {
230
+ if (this.isConnecting) {
231
+ this.printDebug("connect() already in progress, skipping");
232
+ return;
233
+ }
234
+ this.userDisconnect = false;
235
+ this.isConnecting = true;
190
236
  try {
237
+ this.cleanup();
191
238
  await this.getEndpoint();
239
+ if (this.userDisconnect)
240
+ return;
192
241
  await this._connect();
193
242
  } catch (err) {
194
- if (this.config.autoReconnect && !this.userDisconnect) {
195
- this.printDebug(
196
- "Connect failed, retrying in " + this.reconnectInterval / 1e3 + " seconds..."
197
- );
198
- setTimeout(() => this.connect(), this.reconnectInterval);
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();
199
248
  }
249
+ return;
250
+ } finally {
251
+ this.isConnecting = false;
200
252
  }
201
253
  }
202
254
  disconnect() {
203
- var _a;
204
255
  console.info("Disconnecting.");
205
256
  this.userDisconnect = true;
206
- if (this.config.keepAlive && this.heartbeatIntervallId !== void 0) {
207
- clearInterval(this.heartbeatIntervallId);
257
+ if (this.reconnectTimerId) {
258
+ clearTimeout(this.reconnectTimerId);
259
+ this.reconnectTimerId = void 0;
208
260
  }
209
- (_a = this.socket) == null ? void 0 : _a.close();
261
+ this.reconnecting = false;
262
+ this.reconnectAttempts = 0;
263
+ this.cleanup();
264
+ this.connected = false;
265
+ this.registered = false;
210
266
  }
211
267
  heartbeat() {
212
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 reconnectInterval;
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
- reconnectInterval = 1e3;
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,9 +130,39 @@ 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 {
134
168
  this.socket = new WebSocket(this.dw_url, this.sslopts);
@@ -138,8 +172,10 @@ class DWClient extends EventEmitter {
138
172
  reject(err);
139
173
  return;
140
174
  }
175
+ let settled = false;
141
176
  this.socket.on("open", () => {
142
177
  this.connected = true;
178
+ this.reconnectAttempts = 0;
143
179
  console.info("[" + (/* @__PURE__ */ new Date()).toISOString() + "] connect success");
144
180
  if (this.config.keepAlive) {
145
181
  this.isAlive = true;
@@ -155,6 +191,7 @@ class DWClient extends EventEmitter {
155
191
  (_b = this.socket) == null ? void 0 : _b.ping("", true);
156
192
  }, this.heartbeat_interval);
157
193
  }
194
+ settled = true;
158
195
  resolve();
159
196
  });
160
197
  this.socket.on("pong", () => {
@@ -163,16 +200,16 @@ class DWClient extends EventEmitter {
163
200
  this.socket.on("message", (data) => {
164
201
  this.onDownStream(data);
165
202
  });
166
- this.socket.on("close", (err) => {
203
+ this.socket.on("close", () => {
167
204
  this.printDebug("Socket closed");
168
205
  this.connected = false;
169
206
  this.registered = false;
170
- if (this.config.autoReconnect && !this.userDisconnect) {
171
- this.reconnecting = true;
172
- this.printDebug(
173
- "Reconnecting in " + this.reconnectInterval / 1e3 + " seconds..."
174
- );
175
- setTimeout(this.connect.bind(this), this.reconnectInterval);
207
+ if (this.heartbeatIntervallId !== void 0) {
208
+ clearInterval(this.heartbeatIntervallId);
209
+ this.heartbeatIntervallId = void 0;
210
+ }
211
+ if (settled) {
212
+ this.scheduleReconnect();
176
213
  }
177
214
  });
178
215
  this.socket.on("error", (err) => {
@@ -180,31 +217,50 @@ class DWClient extends EventEmitter {
180
217
  this.printDebug("SOCKET ERROR");
181
218
  console.warn("ERROR", err);
182
219
  (_a = this.socket) == null ? void 0 : _a.terminate();
183
- reject(err);
220
+ if (!settled) {
221
+ settled = true;
222
+ reject(err);
223
+ }
184
224
  });
185
225
  });
186
226
  }
187
227
  async connect() {
228
+ if (this.isConnecting) {
229
+ this.printDebug("connect() already in progress, skipping");
230
+ return;
231
+ }
232
+ this.userDisconnect = false;
233
+ this.isConnecting = true;
188
234
  try {
235
+ this.cleanup();
189
236
  await this.getEndpoint();
237
+ if (this.userDisconnect)
238
+ return;
190
239
  await this._connect();
191
240
  } catch (err) {
192
- if (this.config.autoReconnect && !this.userDisconnect) {
193
- this.printDebug(
194
- "Connect failed, retrying in " + this.reconnectInterval / 1e3 + " seconds..."
195
- );
196
- setTimeout(() => this.connect(), this.reconnectInterval);
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();
197
246
  }
247
+ return;
248
+ } finally {
249
+ this.isConnecting = false;
198
250
  }
199
251
  }
200
252
  disconnect() {
201
- var _a;
202
253
  console.info("Disconnecting.");
203
254
  this.userDisconnect = true;
204
- if (this.config.keepAlive && this.heartbeatIntervallId !== void 0) {
205
- clearInterval(this.heartbeatIntervallId);
255
+ if (this.reconnectTimerId) {
256
+ clearTimeout(this.reconnectTimerId);
257
+ this.reconnectTimerId = void 0;
206
258
  }
207
- (_a = this.socket) == null ? void 0 : _a.close();
259
+ this.reconnecting = false;
260
+ this.reconnectAttempts = 0;
261
+ this.cleanup();
262
+ this.connected = false;
263
+ this.registered = false;
208
264
  }
209
265
  heartbeat() {
210
266
  this.isAlive = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dingtalk-stream",
3
- "version": "v2.1.5",
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",