icom-wlan-node 0.2.2 → 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 +1 -0
- package/dist/index.js +5 -1
- package/dist/rig/IcomControl.js +78 -17
- package/dist/utils/errorHandling.d.ts +55 -0
- package/dist/utils/errorHandling.js +147 -0
- package/package.json +1 -1
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; } });
|
package/dist/rig/IcomControl.js
CHANGED
|
@@ -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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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)(
|
|
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)(
|
|
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)(
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
383
|
-
|
|
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
|
-
|
|
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
|
+
}
|