icom-wlan-node 0.2.1 → 0.2.3

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/index.d.ts CHANGED
@@ -4,3 +4,4 @@ export { MODE_MAP, CONNECTOR_MODE_MAP, DEFAULT_CONTROLLER_ADDR, METER_THRESHOLDS
4
4
  export { parseTwoByteBcd, intToTwoByteBcd } from './utils/bcd';
5
5
  export { IcomRigCommands } from './rig/IcomRigCommands';
6
6
  export { AUDIO_RATE } from './rig/IcomAudio';
7
+ export { setupGlobalErrorHandlers, setupBasicErrorProtection, GlobalErrorHandlerOptions } from './utils/errorHandling';
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.AUDIO_RATE = exports.IcomRigCommands = exports.intToTwoByteBcd = exports.parseTwoByteBcd = exports.rawToCurrent = exports.rawToVoltage = exports.rawToPowerPercent = exports.getFilterString = exports.getConnectorModeString = exports.getModeString = exports.getConnectorModeCode = exports.getModeCode = exports.METER_CALIBRATION = exports.METER_THRESHOLDS = exports.DEFAULT_CONTROLLER_ADDR = exports.CONNECTOR_MODE_MAP = exports.MODE_MAP = exports.IcomControl = void 0;
17
+ exports.setupBasicErrorProtection = exports.setupGlobalErrorHandlers = exports.AUDIO_RATE = exports.IcomRigCommands = exports.intToTwoByteBcd = exports.parseTwoByteBcd = exports.rawToCurrent = exports.rawToVoltage = exports.rawToPowerPercent = exports.getFilterString = exports.getConnectorModeString = exports.getModeString = exports.getConnectorModeCode = exports.getModeCode = exports.METER_CALIBRATION = exports.METER_THRESHOLDS = exports.DEFAULT_CONTROLLER_ADDR = exports.CONNECTOR_MODE_MAP = exports.MODE_MAP = exports.IcomControl = void 0;
18
18
  // Export types (includes ConnectionPhase, ConnectionMetrics, etc.)
19
19
  __exportStar(require("./types"), exports);
20
20
  // Export main class
@@ -44,3 +44,7 @@ var IcomRigCommands_1 = require("./rig/IcomRigCommands");
44
44
  Object.defineProperty(exports, "IcomRigCommands", { enumerable: true, get: function () { return IcomRigCommands_1.IcomRigCommands; } });
45
45
  var IcomAudio_1 = require("./rig/IcomAudio");
46
46
  Object.defineProperty(exports, "AUDIO_RATE", { enumerable: true, get: function () { return IcomAudio_1.AUDIO_RATE; } });
47
+ // Export error handling utilities (optional, for robustness)
48
+ var errorHandling_1 = require("./utils/errorHandling");
49
+ Object.defineProperty(exports, "setupGlobalErrorHandlers", { enumerable: true, get: function () { return errorHandling_1.setupGlobalErrorHandlers; } });
50
+ Object.defineProperty(exports, "setupBasicErrorProtection", { enumerable: true, get: function () { return errorHandling_1.setupBasicErrorProtection; } });
@@ -92,14 +92,14 @@ export declare const METER_CALIBRATION: {
92
92
  * Used for converting raw BCD to percentage
93
93
  */
94
94
  readonly POWER: {
95
- /** 50% power reference point */
95
+ /** 50% power reference point (raw=143, percent=50) */
96
96
  readonly HALF: {
97
- readonly raw: 323;
97
+ readonly raw: 143;
98
98
  readonly percent: 50;
99
99
  };
100
- /** 100% power reference point */
100
+ /** 100% power reference point (raw=213, percent=100) */
101
101
  readonly FULL: {
102
- readonly raw: 531;
102
+ readonly raw: 213;
103
103
  readonly percent: 100;
104
104
  };
105
105
  };
@@ -108,14 +108,14 @@ export declare const METER_CALIBRATION: {
108
108
  * Used for converting raw BCD to volts
109
109
  */
110
110
  readonly VOLTAGE: {
111
- /** Low voltage reference: 5V */
111
+ /** Low voltage reference: 5V (raw=75) */
112
112
  readonly LOW: {
113
- readonly raw: 117;
113
+ readonly raw: 75;
114
114
  readonly volts: 5;
115
115
  };
116
- /** High voltage reference: 16V */
116
+ /** High voltage reference: 16V (raw=241) */
117
117
  readonly HIGH: {
118
- readonly raw: 577;
118
+ readonly raw: 241;
119
119
  readonly volts: 16;
120
120
  };
121
121
  };
@@ -124,14 +124,14 @@ export declare const METER_CALIBRATION: {
124
124
  * Used for converting raw BCD to amperes
125
125
  */
126
126
  readonly CURRENT: {
127
- /** Low current reference: 2A */
127
+ /** Low current reference: 2A (raw=121) */
128
128
  readonly LOW: {
129
- readonly raw: 289;
129
+ readonly raw: 121;
130
130
  readonly amps: 2;
131
131
  };
132
- /** High current reference: 4A */
132
+ /** High current reference: 4A (raw=241) */
133
133
  readonly HIGH: {
134
- readonly raw: 577;
134
+ readonly raw: 241;
135
135
  readonly amps: 4;
136
136
  };
137
137
  };
@@ -123,30 +123,30 @@ exports.METER_CALIBRATION = {
123
123
  * Used for converting raw BCD to percentage
124
124
  */
125
125
  POWER: {
126
- /** 50% power reference point */
127
- HALF: { raw: 0x0143, percent: 50 },
128
- /** 100% power reference point */
129
- FULL: { raw: 0x0213, percent: 100 }
126
+ /** 50% power reference point (raw=143, percent=50) */
127
+ HALF: { raw: 143, percent: 50 },
128
+ /** 100% power reference point (raw=213, percent=100) */
129
+ FULL: { raw: 213, percent: 100 }
130
130
  },
131
131
  /**
132
132
  * Voltage (CI-V 0x15/0x15) calibration points
133
133
  * Used for converting raw BCD to volts
134
134
  */
135
135
  VOLTAGE: {
136
- /** Low voltage reference: 5V */
137
- LOW: { raw: 0x0075, volts: 5.0 },
138
- /** High voltage reference: 16V */
139
- HIGH: { raw: 0x0241, volts: 16.0 }
136
+ /** Low voltage reference: 5V (raw=75) */
137
+ LOW: { raw: 75, volts: 5.0 },
138
+ /** High voltage reference: 16V (raw=241) */
139
+ HIGH: { raw: 241, volts: 16.0 }
140
140
  },
141
141
  /**
142
142
  * Current (CI-V 0x15/0x16) calibration points
143
143
  * Used for converting raw BCD to amperes
144
144
  */
145
145
  CURRENT: {
146
- /** Low current reference: 2A */
147
- LOW: { raw: 0x0121, amps: 2.0 },
148
- /** High current reference: 4A */
149
- HIGH: { raw: 0x0241, amps: 4.0 }
146
+ /** Low current reference: 2A (raw=121) */
147
+ LOW: { raw: 121, amps: 2.0 },
148
+ /** High current reference: 4A (raw=241) */
149
+ HIGH: { raw: 241, amps: 4.0 }
150
150
  }
151
151
  };
152
152
  /**
@@ -275,24 +275,48 @@ class IcomControl {
275
275
  const abortHandler = (reason) => {
276
276
  (0, debug_1.dbg)(`Aborting connection session ${sessionId}: ${reason}`);
277
277
  const error = new Error(reason);
278
- rejectLogin(error);
279
- rejectCiv(error);
280
- rejectAudio(error);
278
+ // Defensive error handling: wrap reject calls in try-catch to prevent
279
+ // synchronous errors from propagating if promises are already settled
280
+ try {
281
+ rejectLogin(error);
282
+ }
283
+ catch (err) {
284
+ (0, debug_1.dbg)(`Warning: Failed to reject loginReady promise: ${err}`);
285
+ }
286
+ try {
287
+ rejectCiv(error);
288
+ }
289
+ catch (err) {
290
+ (0, debug_1.dbg)(`Warning: Failed to reject civReady promise: ${err}`);
291
+ }
292
+ try {
293
+ rejectAudio(error);
294
+ }
295
+ catch (err) {
296
+ (0, debug_1.dbg)(`Warning: Failed to reject audioReady promise: ${err}`);
297
+ }
281
298
  };
282
299
  this.abortHandlers.set(sessionId, abortHandler);
300
+ // Track promise states for defensive cleanup
301
+ let loginResolved = false;
302
+ let civResolved = false;
303
+ let audioResolved = false;
283
304
  // Temporary event listeners (local scope)
284
305
  const onLogin = (res) => {
285
306
  if (res.ok) {
286
- (0, debug_1.dbg)('Login ready - resolving local loginReady promise');
307
+ (0, debug_1.dbg)(`Login ready - resolving local loginReady promise (sessionId=${sessionId})`);
308
+ loginResolved = true;
287
309
  resolveLogin();
288
310
  }
289
311
  };
290
312
  const onCivReady = () => {
291
- (0, debug_1.dbg)('CIV ready - resolving local civReady promise');
313
+ (0, debug_1.dbg)(`CIV ready - resolving local civReady promise (sessionId=${sessionId})`);
314
+ civResolved = true;
292
315
  resolveCiv();
293
316
  };
294
317
  const onAudioReady = () => {
295
- (0, debug_1.dbg)('Audio ready - resolving local audioReady promise');
318
+ (0, debug_1.dbg)(`Audio ready - resolving local audioReady promise (sessionId=${sessionId})`);
319
+ audioResolved = true;
296
320
  resolveAudio();
297
321
  };
298
322
  this.ev.once('login', onLogin);
@@ -303,13 +327,36 @@ class IcomControl {
303
327
  civReady,
304
328
  audioReady,
305
329
  cleanup: () => {
330
+ // Defensive cleanup: wrap each cleanup step in try-catch
331
+ // to ensure all cleanup steps execute even if one fails
332
+ (0, debug_1.dbg)(`Cleaning up connection session ${sessionId} (login=${loginResolved}, civ=${civResolved}, audio=${audioResolved})`);
306
333
  // Remove abort handler for this specific sessionId
307
- // This is safe because the connection attempt is complete (success or failure)
308
- this.abortHandlers.delete(sessionId);
309
- // Remove event listeners
310
- this.ev.off('login', onLogin);
311
- this.ev.off('_civReady', onCivReady);
312
- this.ev.off('_audioReady', onAudioReady);
334
+ try {
335
+ this.abortHandlers.delete(sessionId);
336
+ }
337
+ catch (err) {
338
+ (0, debug_1.dbg)(`Warning: Failed to delete abort handler: ${err}`);
339
+ }
340
+ // Remove event listeners - wrap each in try-catch
341
+ try {
342
+ this.ev.off('login', onLogin);
343
+ }
344
+ catch (err) {
345
+ (0, debug_1.dbg)(`Warning: Failed to remove login listener: ${err}`);
346
+ }
347
+ try {
348
+ this.ev.off('_civReady', onCivReady);
349
+ }
350
+ catch (err) {
351
+ (0, debug_1.dbg)(`Warning: Failed to remove civReady listener: ${err}`);
352
+ }
353
+ try {
354
+ this.ev.off('_audioReady', onAudioReady);
355
+ }
356
+ catch (err) {
357
+ (0, debug_1.dbg)(`Warning: Failed to remove audioReady listener: ${err}`);
358
+ }
359
+ (0, debug_1.dbg)(`Cleanup complete for session ${sessionId}`);
313
360
  }
314
361
  };
315
362
  }
@@ -376,11 +423,18 @@ class IcomControl {
376
423
  (0, debug_1.dbg)('disconnect() called but already IDLE - no-op');
377
424
  return;
378
425
  }
379
- // Abort any ongoing connection attempts
426
+ // Abort any ongoing connection attempts - wrap in try-catch for defensive programming
380
427
  const currentSessionId = this.connectionSession.sessionId;
381
428
  if (currentPhase === types_1.ConnectionPhase.CONNECTING || currentPhase === types_1.ConnectionPhase.RECONNECTING) {
382
- (0, debug_1.dbg)(`Aborting ongoing connection attempt (sessionId=${currentSessionId})`);
383
- this.abortConnectionAttempt(currentSessionId, 'User disconnect()');
429
+ try {
430
+ (0, debug_1.dbg)(`Aborting ongoing connection attempt (sessionId=${currentSessionId})`);
431
+ this.abortConnectionAttempt(currentSessionId, 'User disconnect()');
432
+ }
433
+ catch (abortErr) {
434
+ // Log but continue - abort failure shouldn't prevent disconnect
435
+ const errMsg = abortErr instanceof Error ? abortErr.message : String(abortErr);
436
+ (0, debug_1.dbg)(`Warning: Failed to abort connection attempt: ${errMsg} - continuing with disconnect`);
437
+ }
384
438
  }
385
439
  // Transition to DISCONNECTING state
386
440
  this.transitionTo(types_1.ConnectionPhase.DISCONNECTING, 'User disconnect()');
@@ -1544,9 +1598,16 @@ class IcomControl {
1544
1598
  (0, debug_1.dbg)(`Full reconnect attempt #${attempt} (delay: ${delay}ms)`);
1545
1599
  await this.sleep(delay);
1546
1600
  try {
1547
- // Disconnect all sessions
1601
+ // Disconnect all sessions - wrap in try-catch to prevent disconnect errors from aborting reconnect
1548
1602
  (0, debug_1.dbg)('Full reconnect: disconnecting all sessions');
1549
- await this.disconnect();
1603
+ try {
1604
+ await this.disconnect();
1605
+ }
1606
+ catch (disconnectErr) {
1607
+ // Log but don't fail - we still want to attempt reconnect even if disconnect fails
1608
+ const errMsg = disconnectErr instanceof Error ? disconnectErr.message : String(disconnectErr);
1609
+ (0, debug_1.dbg)(`Warning: Disconnect failed during reconnect: ${errMsg} - continuing with reconnect anyway`);
1610
+ }
1550
1611
  // Wait longer before reconnecting to allow rig to fully clean up old connection
1551
1612
  // This is critical - rig may report CONNINFO busy=true if we reconnect too quickly
1552
1613
  (0, debug_1.dbg)('Full reconnect: waiting 5s for rig to clean up old session...');
@@ -0,0 +1,55 @@
1
+ /**
2
+ * 全局错误处理工具函数
3
+ *
4
+ * 这个模块提供可选的全局错误处理器,帮助用户防止未捕获的 Promise rejection 和异常导致进程崩溃。
5
+ *
6
+ * 使用方式:
7
+ * ```typescript
8
+ * import { setupGlobalErrorHandlers } from 'icom-wlan-node/utils/errorHandling';
9
+ *
10
+ * // 在应用启动时调用(可选)
11
+ * setupGlobalErrorHandlers({
12
+ * onUnhandledRejection: (reason, promise) => {
13
+ * console.error('未处理的 Promise rejection:', reason);
14
+ * // 自定义处理逻辑
15
+ * },
16
+ * onUncaughtException: (error, origin) => {
17
+ * console.error('未捕获的异常:', error);
18
+ * // 自定义处理逻辑
19
+ * }
20
+ * });
21
+ * ```
22
+ */
23
+ export interface GlobalErrorHandlerOptions {
24
+ /**
25
+ * 处理未捕获的 Promise rejection
26
+ * @param reason - rejection 的原因
27
+ * @param promise - 被 reject 的 Promise
28
+ */
29
+ onUnhandledRejection?: (reason: any, promise: Promise<any>) => void;
30
+ /**
31
+ * 处理未捕获的异常
32
+ * @param error - 异常对象
33
+ * @param origin - 异常来源 ('uncaughtException' 或 'unhandledRejection')
34
+ */
35
+ onUncaughtException?: (error: Error, origin: string) => void;
36
+ /**
37
+ * 是否在处理错误后阻止进程退出(默认 true)
38
+ * 设为 false 时,错误会被记录但进程仍可能退出
39
+ */
40
+ preventExit?: boolean;
41
+ }
42
+ /**
43
+ * 设置全局错误处理器
44
+ *
45
+ * 注意:这是一个可选的工具函数,仅在需要时使用。
46
+ * 如果你的应用已经有全局错误处理逻辑,不需要调用此函数。
47
+ *
48
+ * @param options - 错误处理选项
49
+ * @returns cleanup 函数,用于移除错误处理器
50
+ */
51
+ export declare function setupGlobalErrorHandlers(options?: GlobalErrorHandlerOptions): () => void;
52
+ /**
53
+ * 快速设置:仅防止进程崩溃,使用默认错误处理
54
+ */
55
+ export declare function setupBasicErrorProtection(): () => void;
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ /**
3
+ * 全局错误处理工具函数
4
+ *
5
+ * 这个模块提供可选的全局错误处理器,帮助用户防止未捕获的 Promise rejection 和异常导致进程崩溃。
6
+ *
7
+ * 使用方式:
8
+ * ```typescript
9
+ * import { setupGlobalErrorHandlers } from 'icom-wlan-node/utils/errorHandling';
10
+ *
11
+ * // 在应用启动时调用(可选)
12
+ * setupGlobalErrorHandlers({
13
+ * onUnhandledRejection: (reason, promise) => {
14
+ * console.error('未处理的 Promise rejection:', reason);
15
+ * // 自定义处理逻辑
16
+ * },
17
+ * onUncaughtException: (error, origin) => {
18
+ * console.error('未捕获的异常:', error);
19
+ * // 自定义处理逻辑
20
+ * }
21
+ * });
22
+ * ```
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.setupGlobalErrorHandlers = setupGlobalErrorHandlers;
26
+ exports.setupBasicErrorProtection = setupBasicErrorProtection;
27
+ /**
28
+ * 设置全局错误处理器
29
+ *
30
+ * 注意:这是一个可选的工具函数,仅在需要时使用。
31
+ * 如果你的应用已经有全局错误处理逻辑,不需要调用此函数。
32
+ *
33
+ * @param options - 错误处理选项
34
+ * @returns cleanup 函数,用于移除错误处理器
35
+ */
36
+ function setupGlobalErrorHandlers(options = {}) {
37
+ const { onUnhandledRejection = defaultUnhandledRejectionHandler, onUncaughtException = defaultUncaughtExceptionHandler, preventExit = true } = options;
38
+ // 未处理的 Promise rejection 处理器
39
+ const unhandledRejectionHandler = (reason, promise) => {
40
+ console.error('⚠️ [icom-wlan-node] 检测到未处理的 Promise rejection:');
41
+ console.error('原因:', reason);
42
+ console.error('Promise:', promise);
43
+ if (reason instanceof Error) {
44
+ console.error('错误堆栈:', reason.stack);
45
+ }
46
+ // 调用用户自定义处理器
47
+ onUnhandledRejection(reason, promise);
48
+ };
49
+ // 未捕获的异常处理器
50
+ const uncaughtExceptionHandler = (error, origin) => {
51
+ console.error('⚠️ [icom-wlan-node] 检测到未捕获的异常:');
52
+ console.error('错误:', error);
53
+ console.error('来源:', origin);
54
+ console.error('堆栈:', error.stack);
55
+ // 调用用户自定义处理器
56
+ onUncaughtException(error, origin);
57
+ // 根据配置决定是否退出
58
+ if (!preventExit) {
59
+ console.error('进程即将退出...');
60
+ process.exit(1);
61
+ }
62
+ };
63
+ // 注册处理器
64
+ process.on('unhandledRejection', unhandledRejectionHandler);
65
+ process.on('uncaughtException', uncaughtExceptionHandler);
66
+ console.log('✓ [icom-wlan-node] 全局错误处理器已设置');
67
+ // 返回 cleanup 函数
68
+ return () => {
69
+ process.off('unhandledRejection', unhandledRejectionHandler);
70
+ process.off('uncaughtException', uncaughtExceptionHandler);
71
+ console.log('✓ [icom-wlan-node] 全局错误处理器已移除');
72
+ };
73
+ }
74
+ /**
75
+ * 默认的未处理 Promise rejection 处理器
76
+ */
77
+ function defaultUnhandledRejectionHandler(reason, promise) {
78
+ // 检查是否是网络错误(可以优雅处理)
79
+ if (isNetworkError(reason)) {
80
+ console.warn('网络错误已被捕获,但未影响进程稳定性');
81
+ return;
82
+ }
83
+ // 其他错误记录为警告
84
+ console.warn('检测到未处理的 Promise rejection,但进程继续运行');
85
+ }
86
+ /**
87
+ * 默认的未捕获异常处理器
88
+ */
89
+ function defaultUncaughtExceptionHandler(error, origin) {
90
+ // 检查是否是可恢复的错误
91
+ if (isRecoverableError(error)) {
92
+ console.warn('可恢复的错误已被捕获,进程继续运行');
93
+ return;
94
+ }
95
+ console.error('检测到严重错误,建议检查应用逻辑');
96
+ }
97
+ /**
98
+ * 判断是否是网络错误
99
+ */
100
+ function isNetworkError(error) {
101
+ if (!error)
102
+ return false;
103
+ const networkErrorCodes = [
104
+ 'EHOSTDOWN',
105
+ 'EHOSTUNREACH',
106
+ 'ENETDOWN',
107
+ 'ENETUNREACH',
108
+ 'ECONNREFUSED',
109
+ 'ECONNRESET',
110
+ 'ETIMEDOUT',
111
+ 'ENOTFOUND'
112
+ ];
113
+ // 检查错误码
114
+ if (error.code && networkErrorCodes.includes(error.code)) {
115
+ return true;
116
+ }
117
+ // 检查错误消息
118
+ if (error.message && typeof error.message === 'string') {
119
+ return networkErrorCodes.some(code => error.message.includes(code));
120
+ }
121
+ return false;
122
+ }
123
+ /**
124
+ * 判断是否是可恢复的错误
125
+ */
126
+ function isRecoverableError(error) {
127
+ // 网络错误通常是可恢复的
128
+ if (isNetworkError(error)) {
129
+ return true;
130
+ }
131
+ // 连接相关的错误也是可恢复的
132
+ const recoverableMessages = [
133
+ 'Connection failed',
134
+ 'User disconnect',
135
+ 'Connection timeout',
136
+ 'CIV/Audio sessions timeout'
137
+ ];
138
+ return recoverableMessages.some(msg => error.message.includes(msg));
139
+ }
140
+ /**
141
+ * 快速设置:仅防止进程崩溃,使用默认错误处理
142
+ */
143
+ function setupBasicErrorProtection() {
144
+ return setupGlobalErrorHandlers({
145
+ preventExit: true
146
+ });
147
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "icom-wlan-node",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Icom WLAN (CI‑V, audio) protocol implementation for Node.js/TypeScript.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",