@zhin.js/adapter 1.2.1 → 1.2.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.
@@ -41,7 +41,7 @@ export interface EndpointConnectHandle {
41
41
  * 初始连接失败由 start() 的拒绝路径复位,不武装重连。
42
42
  */
43
43
  notifyClosed(reason?: unknown): void;
44
- /** 注册当前连接的强制关闭函数(心跳看门狗与 stop 使用);每次 connect 覆盖。 */
44
+ /** 注册当前连接的强制关闭函数;同代已 stop 时立即关闭,旧代句柄忽略。 */
45
45
  onForceClose(close: () => void): void;
46
46
  }
47
47
  export type EndpointConnectFn = (handle: EndpointConnectHandle) => Promise<void>;
@@ -102,19 +102,20 @@ class EndpointLifecycleImpl {
102
102
  this.#connect = connect;
103
103
  this.#state = 'connecting';
104
104
  this.#attempt = 0;
105
+ const generation = this.#generation + 1;
105
106
  try {
106
107
  await this.#runConnect(connect);
107
108
  }
108
109
  catch (err) {
109
110
  // 注意:stop() 可能在 await 期间并发改写 #state,必须经 getter 读取避免 TS 窄化误判
110
- if (this.#currentState() === 'stopped')
111
- return; // stop-during-connect 竞态:静默 settle
111
+ if (generation !== this.#generation || this.#currentState() === 'stopped')
112
+ return;
112
113
  // start 失败复位:回 idle、不武装重连,允许调用方重试
113
114
  this.#state = 'idle';
114
115
  throw err;
115
116
  }
116
- if (this.#currentState() === 'stopped')
117
- return; // stop 竞态先于 open
117
+ if (generation !== this.#generation || this.#currentState() === 'stopped')
118
+ return;
118
119
  this.#state = 'open';
119
120
  }
120
121
  async stop() {
@@ -220,8 +221,18 @@ class EndpointLifecycleImpl {
220
221
  this.#scheduleReconnect();
221
222
  },
222
223
  onForceClose: (close) => {
223
- if (generation === this.#generation)
224
- this.#forceClose = close;
224
+ if (generation !== this.#generation)
225
+ return;
226
+ if (this.#state === 'stopped') {
227
+ try {
228
+ close();
229
+ }
230
+ catch {
231
+ /* same best-effort cleanup as stop() */
232
+ }
233
+ return;
234
+ }
235
+ this.#forceClose = close;
225
236
  },
226
237
  };
227
238
  }
@@ -231,7 +242,11 @@ class EndpointLifecycleImpl {
231
242
  this.#forceClose = undefined;
232
243
  const handle = this.#createHandle(generation);
233
244
  // Promise.resolve().then 兜底同步抛错;额外 catch 防止 stop 竞态后迟到拒绝变 unhandled
234
- const connecting = Promise.resolve().then(() => connect(handle));
245
+ const connecting = Promise.resolve().then(() => {
246
+ if (generation !== this.#generation || this.#state === 'stopped')
247
+ return;
248
+ return connect(handle);
249
+ });
235
250
  connecting.catch(() => { });
236
251
  let wake;
237
252
  const stopped = new Promise((resolve) => {
@@ -285,11 +300,12 @@ class EndpointLifecycleImpl {
285
300
  const elapsed = await this.#sleep(delay);
286
301
  if (!elapsed || this.#currentState() !== 'reconnecting')
287
302
  return;
303
+ const generation = this.#generation + 1;
288
304
  try {
289
305
  await this.#runConnect(connect);
290
306
  }
291
307
  catch (err) {
292
- if (this.#currentState() === 'stopped')
308
+ if (generation !== this.#generation || this.#currentState() === 'stopped')
293
309
  return;
294
310
  this.#attempt += 1;
295
311
  logger.debug(formatCompact({
@@ -301,7 +317,7 @@ class EndpointLifecycleImpl {
301
317
  }));
302
318
  continue;
303
319
  }
304
- if (this.#currentState() === 'stopped')
320
+ if (generation !== this.#generation || this.#currentState() === 'stopped')
305
321
  return;
306
322
  this.#state = 'open';
307
323
  this.#attempt = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/adapter",
3
- "version": "1.2.1",
3
+ "version": "1.2.2",
4
4
  "description": "Convention-based Adapter and Endpoint Feature for Zhin Plugin Runtime",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -99,7 +99,7 @@ export interface EndpointConnectHandle {
99
99
  * 初始连接失败由 start() 的拒绝路径复位,不武装重连。
100
100
  */
101
101
  notifyClosed(reason?: unknown): void;
102
- /** 注册当前连接的强制关闭函数(心跳看门狗与 stop 使用);每次 connect 覆盖。 */
102
+ /** 注册当前连接的强制关闭函数;同代已 stop 时立即关闭,旧代句柄忽略。 */
103
103
  onForceClose(close: () => void): void;
104
104
  }
105
105
 
@@ -197,16 +197,17 @@ class EndpointLifecycleImpl implements EndpointLifecycle {
197
197
  this.#connect = connect;
198
198
  this.#state = 'connecting';
199
199
  this.#attempt = 0;
200
+ const generation = this.#generation + 1;
200
201
  try {
201
202
  await this.#runConnect(connect);
202
203
  } catch (err) {
203
204
  // 注意:stop() 可能在 await 期间并发改写 #state,必须经 getter 读取避免 TS 窄化误判
204
- if (this.#currentState() === 'stopped') return; // stop-during-connect 竞态:静默 settle
205
+ if (generation !== this.#generation || this.#currentState() === 'stopped') return;
205
206
  // start 失败复位:回 idle、不武装重连,允许调用方重试
206
207
  this.#state = 'idle';
207
208
  throw err;
208
209
  }
209
- if (this.#currentState() === 'stopped') return; // stop 竞态先于 open
210
+ if (generation !== this.#generation || this.#currentState() === 'stopped') return;
210
211
  this.#state = 'open';
211
212
  }
212
213
 
@@ -310,7 +311,16 @@ class EndpointLifecycleImpl implements EndpointLifecycle {
310
311
  this.#scheduleReconnect();
311
312
  },
312
313
  onForceClose: (close) => {
313
- if (generation === this.#generation) this.#forceClose = close;
314
+ if (generation !== this.#generation) return;
315
+ if (this.#state === 'stopped') {
316
+ try {
317
+ close();
318
+ } catch {
319
+ /* same best-effort cleanup as stop() */
320
+ }
321
+ return;
322
+ }
323
+ this.#forceClose = close;
314
324
  },
315
325
  };
316
326
  }
@@ -321,7 +331,10 @@ class EndpointLifecycleImpl implements EndpointLifecycle {
321
331
  this.#forceClose = undefined;
322
332
  const handle = this.#createHandle(generation);
323
333
  // Promise.resolve().then 兜底同步抛错;额外 catch 防止 stop 竞态后迟到拒绝变 unhandled
324
- const connecting = Promise.resolve().then(() => connect(handle));
334
+ const connecting = Promise.resolve().then(() => {
335
+ if (generation !== this.#generation || this.#state === 'stopped') return;
336
+ return connect(handle);
337
+ });
325
338
  connecting.catch(() => { /* settled via race; late rejection ignored */ });
326
339
  let wake!: () => void;
327
340
  const stopped = new Promise<void>((resolve) => {
@@ -375,10 +388,11 @@ class EndpointLifecycleImpl implements EndpointLifecycle {
375
388
  }));
376
389
  const elapsed = await this.#sleep(delay);
377
390
  if (!elapsed || this.#currentState() !== 'reconnecting') return;
391
+ const generation = this.#generation + 1;
378
392
  try {
379
393
  await this.#runConnect(connect);
380
394
  } catch (err) {
381
- if (this.#currentState() === 'stopped') return;
395
+ if (generation !== this.#generation || this.#currentState() === 'stopped') return;
382
396
  this.#attempt += 1;
383
397
  logger.debug(formatCompact({
384
398
  op: 'reconnect',
@@ -389,7 +403,7 @@ class EndpointLifecycleImpl implements EndpointLifecycle {
389
403
  }));
390
404
  continue;
391
405
  }
392
- if (this.#currentState() === 'stopped') return;
406
+ if (generation !== this.#generation || this.#currentState() === 'stopped') return;
393
407
  this.#state = 'open';
394
408
  this.#attempt = 0;
395
409
  logger.info(formatCompact({ op: 'reconnect', endpoint: this.#name, ok: true }));