@onekeyfe/hd-core 1.2.0-alpha.134 → 1.2.0-alpha.136
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/__tests__/DeviceCommands.test.ts +7 -0
- package/__tests__/device-lifecycle-events.test.ts +91 -2
- package/__tests__/device-settings.test.ts +167 -9
- package/__tests__/protocol-v2.test.ts +34 -1
- package/dist/api/device/DeviceSettings.d.ts.map +1 -1
- package/dist/api/protocol-v2/DeviceUploadWallpaper.d.ts.map +1 -1
- package/dist/core/index.d.ts +3 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/device/Device.d.ts +2 -0
- package/dist/device/Device.d.ts.map +1 -1
- package/dist/device/DeviceCommands.d.ts.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +258 -189
- package/package.json +4 -4
- package/src/api/device/DeviceSettings.ts +2 -16
- package/src/api/protocol-v2/DeviceUploadWallpaper.ts +11 -0
- package/src/core/index.ts +67 -25
- package/src/device/Device.ts +30 -2
- package/src/device/DeviceCommands.ts +14 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-core",
|
|
3
|
-
"version": "1.2.0-alpha.
|
|
3
|
+
"version": "1.2.0-alpha.136",
|
|
4
4
|
"description": "Core processes and APIs for communicating with OneKey hardware devices.",
|
|
5
5
|
"author": "OneKey",
|
|
6
6
|
"homepage": "https://github.com/OneKeyHQ/hardware-js-sdk#readme",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"url": "https://github.com/OneKeyHQ/hardware-js-sdk/issues"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@onekeyfe/hd-shared": "1.2.0-alpha.
|
|
29
|
-
"@onekeyfe/hd-transport": "1.2.0-alpha.
|
|
28
|
+
"@onekeyfe/hd-shared": "1.2.0-alpha.136",
|
|
29
|
+
"@onekeyfe/hd-transport": "1.2.0-alpha.136",
|
|
30
30
|
"axios": "1.15.2",
|
|
31
31
|
"bignumber.js": "^9.0.2",
|
|
32
32
|
"buffer": "^6.0.3",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"@types/w3c-web-usb": "^1.0.10",
|
|
47
47
|
"@types/web-bluetooth": "^0.0.21"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "92945bc4aae3d6602dd44c2e028e902e6bce7b55"
|
|
50
50
|
}
|
|
@@ -168,8 +168,7 @@ export default class DeviceSettings extends BaseMethod<ApplySettings> {
|
|
|
168
168
|
async run() {
|
|
169
169
|
try {
|
|
170
170
|
if (this.device.isProtocolV2()) {
|
|
171
|
-
const refreshStatusAndSettings = () =>
|
|
172
|
-
this.device.getDeviceState({ refreshSections: ['status', 'settings'] });
|
|
171
|
+
const refreshStatusAndSettings = () => this.device.refreshProtocolV2SettingsAfterMutation();
|
|
173
172
|
assertSettingsSupported(this.payload, DEVICE_SETTINGS_V1_ONLY_FIELDS, 'Protocol V2');
|
|
174
173
|
const capabilities = getDeviceSettingsCapabilities(
|
|
175
174
|
this.device.getCurrentDeviceType(),
|
|
@@ -201,20 +200,7 @@ export default class DeviceSettings extends BaseMethod<ApplySettings> {
|
|
|
201
200
|
const res = await this.device.commands.typedCall('DeviceSettingsPageShow', 'Success', {
|
|
202
201
|
page: DeviceSettingsPage.DevicePassphrase,
|
|
203
202
|
});
|
|
204
|
-
|
|
205
|
-
const lockedAfterDisabling =
|
|
206
|
-
requestedPassphrase === false &&
|
|
207
|
-
current.status.unlocked === true &&
|
|
208
|
-
updated.status.unlocked === false;
|
|
209
|
-
if (
|
|
210
|
-
updated.status.passphraseProtection !== requestedPassphrase &&
|
|
211
|
-
!lockedAfterDisabling
|
|
212
|
-
) {
|
|
213
|
-
throw TypedError(
|
|
214
|
-
HardwareErrorCode.RuntimeError,
|
|
215
|
-
'Protocol V2 passphrase setting did not reach the requested value.'
|
|
216
|
-
);
|
|
217
|
-
}
|
|
203
|
+
await refreshStatusAndSettings();
|
|
218
204
|
return res.message;
|
|
219
205
|
}
|
|
220
206
|
if (requestedAirgap !== undefined) {
|
|
@@ -8,6 +8,7 @@ import { invalidParameter } from '../helpers/filesystemValidation';
|
|
|
8
8
|
import { writeProtocolV2File } from '../helpers/protocolV2FileWrite';
|
|
9
9
|
import { UI_REQUEST, createUiMessage } from '../../events/ui-request';
|
|
10
10
|
import { supportsProtocolV2Message } from '../../protocols/protocol-v2/features';
|
|
11
|
+
import { LoggerNames, getLogger } from '../../utils';
|
|
11
12
|
import {
|
|
12
13
|
PRO2_WALLPAPER_HEIGHT,
|
|
13
14
|
PRO2_WALLPAPER_WIDTH,
|
|
@@ -33,6 +34,7 @@ const SAFE_FILE_NAME = /^[A-Za-z0-9_-]+(?:\.bin)?$/;
|
|
|
33
34
|
const DEVICE_SETTINGS_SET_MESSAGE_TYPE = 60412;
|
|
34
35
|
const FILESYSTEM_FILE_WRITE_MESSAGE_TYPE = 60805;
|
|
35
36
|
const FILESYSTEM_DIR_MAKE_MESSAGE_TYPE = 60809;
|
|
37
|
+
const Log = getLogger(LoggerNames.Method);
|
|
36
38
|
|
|
37
39
|
function normalizeFileName(fileName: string | undefined, data: Uint8Array): string {
|
|
38
40
|
if (fileName !== undefined && (!fileName || !SAFE_FILE_NAME.test(fileName))) {
|
|
@@ -143,6 +145,15 @@ export default class DeviceUploadWallpaper extends BaseMethod<DeviceUploadWallpa
|
|
|
143
145
|
const response = await this.device.commands.typedCall('DeviceSettingsSet', 'Success', {
|
|
144
146
|
settings: { wallpaper_path: this.path },
|
|
145
147
|
});
|
|
148
|
+
try {
|
|
149
|
+
await this.device.refreshProtocolV2SettingsAfterMutation();
|
|
150
|
+
} catch (error) {
|
|
151
|
+
// The wallpaper is already applied. A transient read-back failure must not
|
|
152
|
+
// make callers retry the completed file upload.
|
|
153
|
+
Log.warn('Protocol V2 wallpaper settings refresh failed after apply', {
|
|
154
|
+
error: error instanceof Error ? error.message : String(error),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
146
157
|
return {
|
|
147
158
|
path: this.path,
|
|
148
159
|
size: encoded.data.byteLength,
|
package/src/core/index.ts
CHANGED
|
@@ -83,6 +83,7 @@ import type { BaseMethod } from '../api/BaseMethod';
|
|
|
83
83
|
|
|
84
84
|
const Log = getLogger(LoggerNames.Core);
|
|
85
85
|
const PRE_INITIALIZE_TTL_MS = 60 * 1000;
|
|
86
|
+
const PRE_PENDING_CALL_TIMEOUT_MS = 15 * 1000;
|
|
86
87
|
|
|
87
88
|
// Dedup/coalesce state for "pre-warm signal" methods (isPreWarmSignal),
|
|
88
89
|
// keyed by getPreWarmKey(): coalesce in-flight, skip if warmed within TTL.
|
|
@@ -296,26 +297,35 @@ const handlePreWarmSignal = async (
|
|
|
296
297
|
}
|
|
297
298
|
};
|
|
298
299
|
|
|
299
|
-
const waitWithTimeout = async (promise: Promise<any>, timeout: number) => {
|
|
300
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
301
|
-
setTimeout(() => reject(new Error('Request timeout')), timeout);
|
|
302
|
-
});
|
|
303
|
-
return Promise.race([promise, timeoutPromise]);
|
|
304
|
-
};
|
|
305
|
-
|
|
306
300
|
const waitForPendingPromise = async (
|
|
307
|
-
|
|
308
|
-
|
|
301
|
+
connectId: string,
|
|
302
|
+
getPrePendingCallPromise: (connectId: string) => Promise<void> | undefined,
|
|
303
|
+
removePrePendingCallPromise?: (connectId: string, promise: Promise<void>) => void
|
|
309
304
|
) => {
|
|
310
|
-
const pendingPromise = getPrePendingCallPromise();
|
|
305
|
+
const pendingPromise = getPrePendingCallPromise(connectId);
|
|
311
306
|
if (pendingPromise) {
|
|
312
307
|
Log.debug('pre pending call promise before call method, wait for it');
|
|
308
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
309
|
+
let timedOut = false;
|
|
313
310
|
try {
|
|
314
|
-
await
|
|
311
|
+
await Promise.race([
|
|
312
|
+
pendingPromise,
|
|
313
|
+
new Promise<void>(resolve => {
|
|
314
|
+
timer = setTimeout(() => {
|
|
315
|
+
timedOut = true;
|
|
316
|
+
resolve();
|
|
317
|
+
}, PRE_PENDING_CALL_TIMEOUT_MS);
|
|
318
|
+
}),
|
|
319
|
+
]);
|
|
315
320
|
} catch (error) {
|
|
316
|
-
//
|
|
321
|
+
// Cancellation is best-effort; the transport teardown owns recovery.
|
|
322
|
+
} finally {
|
|
323
|
+
if (timer) clearTimeout(timer);
|
|
324
|
+
removePrePendingCallPromise?.(connectId, pendingPromise);
|
|
325
|
+
}
|
|
326
|
+
if (timedOut) {
|
|
327
|
+
Log.warn('pre pending call promise timed out before call method', { connectId });
|
|
317
328
|
}
|
|
318
|
-
removePrePendingCallPromise?.(pendingPromise);
|
|
319
329
|
Log.debug('pre pending call promise before call method done');
|
|
320
330
|
}
|
|
321
331
|
};
|
|
@@ -327,7 +337,7 @@ const onCallDevice = async (
|
|
|
327
337
|
): Promise<any> => {
|
|
328
338
|
let messageResponse: any;
|
|
329
339
|
|
|
330
|
-
const { requestQueue, getPrePendingCallPromise,
|
|
340
|
+
const { requestQueue, getPrePendingCallPromise, removePrePendingCallPromise } = context;
|
|
331
341
|
|
|
332
342
|
updateMethodRequestContext(method, { status: 'running' });
|
|
333
343
|
|
|
@@ -355,7 +365,11 @@ const onCallDevice = async (
|
|
|
355
365
|
await context.waitForCallbackTasks(method.connectId);
|
|
356
366
|
}
|
|
357
367
|
|
|
358
|
-
await waitForPendingPromise(
|
|
368
|
+
await waitForPendingPromise(
|
|
369
|
+
method.connectId ?? '',
|
|
370
|
+
getPrePendingCallPromise,
|
|
371
|
+
removePrePendingCallPromise
|
|
372
|
+
);
|
|
359
373
|
|
|
360
374
|
const task = requestQueue.createTask(method);
|
|
361
375
|
|
|
@@ -454,7 +468,11 @@ const onCallDevice = async (
|
|
|
454
468
|
await context.waitForCallbackTasks(method.connectId, preWarmCallbackTask);
|
|
455
469
|
}
|
|
456
470
|
|
|
457
|
-
await waitForPendingPromise(
|
|
471
|
+
await waitForPendingPromise(
|
|
472
|
+
method.connectId ?? '',
|
|
473
|
+
getPrePendingCallPromise,
|
|
474
|
+
removePrePendingCallPromise
|
|
475
|
+
);
|
|
458
476
|
|
|
459
477
|
const inner = async (): Promise<void> => {
|
|
460
478
|
// Protocol is established from an active device response during acquire/initialize.
|
|
@@ -906,6 +924,16 @@ function isRetryableBleProtocolV2ProbeError(method: BaseMethod, error: unknown)
|
|
|
906
924
|
);
|
|
907
925
|
}
|
|
908
926
|
|
|
927
|
+
export function isMissingDetectedProtocolV2Error(method: BaseMethod, error: unknown) {
|
|
928
|
+
const typedError = error as { errorCode?: unknown; message?: unknown };
|
|
929
|
+
return (
|
|
930
|
+
method.payload.connectProtocol === 'V2' &&
|
|
931
|
+
typedError?.errorCode === HardwareErrorCode.RuntimeError &&
|
|
932
|
+
typeof typedError.message === 'string' &&
|
|
933
|
+
typedError.message.includes('Device protocol has not been detected')
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
|
|
909
937
|
/**
|
|
910
938
|
* If the Bluetooth connection times out, retry up to 6 times
|
|
911
939
|
* @param retryCount - Current retry count (default 0)
|
|
@@ -943,12 +971,13 @@ async function connectDeviceForBle(method: BaseMethod, device: Device, retryCoun
|
|
|
943
971
|
DevicePool.emitter.emit(DEVICE.CONNECT, device);
|
|
944
972
|
}
|
|
945
973
|
} catch (err) {
|
|
974
|
+
const requiresColdReconnect = isMissingDetectedProtocolV2Error(method, err);
|
|
946
975
|
// Device.run()'s REQUIRE_DISCONNECT handling never sees acquire/initialize
|
|
947
976
|
// failures, so with keep-alive a wedged link would be reused by every retry
|
|
948
977
|
// (field case: Initialize timing out at 25s per attempt, forever). Drop it
|
|
949
978
|
// here so the next retry cold-connects.
|
|
950
979
|
if (
|
|
951
|
-
ERROR_CODES_REQUIRE_DISCONNECT.includes(err.errorCode) &&
|
|
980
|
+
(ERROR_CODES_REQUIRE_DISCONNECT.includes(err.errorCode) || requiresColdReconnect) &&
|
|
952
981
|
device.mainId &&
|
|
953
982
|
device.deviceConnector
|
|
954
983
|
) {
|
|
@@ -961,11 +990,12 @@ async function connectDeviceForBle(method: BaseMethod, device: Device, retryCoun
|
|
|
961
990
|
if (
|
|
962
991
|
(err.errorCode === HardwareErrorCode.BleTimeoutError ||
|
|
963
992
|
err.errorCode === HardwareErrorCode.BleConnectedError ||
|
|
964
|
-
isRetryableBleProtocolV2ProbeError(method, err)
|
|
993
|
+
isRetryableBleProtocolV2ProbeError(method, err) ||
|
|
994
|
+
requiresColdReconnect) &&
|
|
965
995
|
retryCount < 6
|
|
966
996
|
) {
|
|
967
997
|
const nextRetry = retryCount + 1;
|
|
968
|
-
Log.debug(`Bluetooth
|
|
998
|
+
Log.debug(`Bluetooth connection will retry, retry count: ${nextRetry}`);
|
|
969
999
|
await wait(3000);
|
|
970
1000
|
await connectDeviceForBle(method, device, nextRetry);
|
|
971
1001
|
} else {
|
|
@@ -1177,7 +1207,10 @@ export const cancel = (context: CoreContext, connectId?: string) => {
|
|
|
1177
1207
|
if (task && task.method?.device) {
|
|
1178
1208
|
if (!canceledDevices.includes(task.method.device)) {
|
|
1179
1209
|
const { device } = task.method;
|
|
1180
|
-
setPrePendingCallPromise(
|
|
1210
|
+
setPrePendingCallPromise(
|
|
1211
|
+
task.method.connectId ?? connectId,
|
|
1212
|
+
device.interruptionFromUser()
|
|
1213
|
+
);
|
|
1181
1214
|
canceledDevices.push(device);
|
|
1182
1215
|
}
|
|
1183
1216
|
requestQueue.rejectRequest(
|
|
@@ -1531,7 +1564,7 @@ export default class Core extends EventEmitter {
|
|
|
1531
1564
|
private disposePromise?: Promise<void>;
|
|
1532
1565
|
|
|
1533
1566
|
// background task
|
|
1534
|
-
private
|
|
1567
|
+
private prePendingCallPromises = new Map<string, Promise<void>>();
|
|
1535
1568
|
|
|
1536
1569
|
private methodSynchronize = getSynchronize();
|
|
1537
1570
|
|
|
@@ -1548,9 +1581,18 @@ export default class Core extends EventEmitter {
|
|
|
1548
1581
|
tracingContext: this.tracingContext,
|
|
1549
1582
|
requestQueue: this.requestQueue,
|
|
1550
1583
|
methodSynchronize: this.methodSynchronize,
|
|
1551
|
-
getPrePendingCallPromise: () => this.
|
|
1552
|
-
setPrePendingCallPromise: (
|
|
1553
|
-
|
|
1584
|
+
getPrePendingCallPromise: (connectId: string) => this.prePendingCallPromises.get(connectId),
|
|
1585
|
+
setPrePendingCallPromise: (connectId: string, promise?: Promise<void>) => {
|
|
1586
|
+
if (!promise) {
|
|
1587
|
+
this.prePendingCallPromises.delete(connectId);
|
|
1588
|
+
return;
|
|
1589
|
+
}
|
|
1590
|
+
this.prePendingCallPromises.set(connectId, promise);
|
|
1591
|
+
},
|
|
1592
|
+
removePrePendingCallPromise: (connectId: string, promise: Promise<void>) => {
|
|
1593
|
+
if (this.prePendingCallPromises.get(connectId) === promise) {
|
|
1594
|
+
this.prePendingCallPromises.delete(connectId);
|
|
1595
|
+
}
|
|
1554
1596
|
},
|
|
1555
1597
|
// callback 任务管理
|
|
1556
1598
|
registerCallbackTask: (connectId: string, callbackPromise: Deferred<any>) => {
|
|
@@ -1665,7 +1707,7 @@ export default class Core extends EventEmitter {
|
|
|
1665
1707
|
preWarmInflight.clear();
|
|
1666
1708
|
preWarmDoneAt.clear();
|
|
1667
1709
|
preConnectCache = { passphraseState: undefined };
|
|
1668
|
-
this.
|
|
1710
|
+
this.prePendingCallPromises.clear();
|
|
1669
1711
|
this.removeAllListeners();
|
|
1670
1712
|
cleanupSdkInstance(this.sdkInstanceId);
|
|
1671
1713
|
if (_core === this) _core = undefined;
|
package/src/device/Device.ts
CHANGED
|
@@ -284,6 +284,9 @@ export class Device extends EventEmitter {
|
|
|
284
284
|
|
|
285
285
|
runPromise?: Deferred<void> | null;
|
|
286
286
|
|
|
287
|
+
/** Resolves only after the active run has completed its release path. */
|
|
288
|
+
private runCleanupPromise?: Promise<void>;
|
|
289
|
+
|
|
287
290
|
externalState: string[] = [];
|
|
288
291
|
|
|
289
292
|
unavailableCapabilities: UnavailableCapabilities = {};
|
|
@@ -1107,6 +1110,10 @@ export class Device extends EventEmitter {
|
|
|
1107
1110
|
return params.includeRaw ? cloneDeviceState(this.state) : createPublicDeviceState(this.state);
|
|
1108
1111
|
}
|
|
1109
1112
|
|
|
1113
|
+
async refreshProtocolV2SettingsAfterMutation() {
|
|
1114
|
+
return this.getDeviceState({ refreshSections: ['status', 'settings'] });
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1110
1117
|
_updateFeatures(protoFeatures: PROTO.Features | Features, initSession?: boolean) {
|
|
1111
1118
|
const previousDeviceId = this.getCurrentDeviceId();
|
|
1112
1119
|
let feat =
|
|
@@ -1424,12 +1431,20 @@ export class Device extends EventEmitter {
|
|
|
1424
1431
|
|
|
1425
1432
|
const runPromise = createDeferred<void>();
|
|
1426
1433
|
this.runPromise = runPromise;
|
|
1427
|
-
this._runInner(fn, options, runPromise).catch(error => {
|
|
1434
|
+
const cleanupPromise = this._runInner(fn, options, runPromise).catch(error => {
|
|
1428
1435
|
if (this.runPromise === runPromise) {
|
|
1429
1436
|
this.runPromise = null;
|
|
1430
1437
|
}
|
|
1431
1438
|
runPromise.reject(error);
|
|
1432
1439
|
});
|
|
1440
|
+
this.runCleanupPromise = cleanupPromise;
|
|
1441
|
+
cleanupPromise
|
|
1442
|
+
.finally(() => {
|
|
1443
|
+
if (this.runCleanupPromise === cleanupPromise) {
|
|
1444
|
+
this.runCleanupPromise = undefined;
|
|
1445
|
+
}
|
|
1446
|
+
})
|
|
1447
|
+
.catch(() => undefined);
|
|
1433
1448
|
return runPromise.promise;
|
|
1434
1449
|
}
|
|
1435
1450
|
|
|
@@ -1541,13 +1556,26 @@ export class Device extends EventEmitter {
|
|
|
1541
1556
|
|
|
1542
1557
|
async interruptionFromUser() {
|
|
1543
1558
|
const error = ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
|
|
1544
|
-
|
|
1559
|
+
const cleanupPromise = this.runCleanupPromise;
|
|
1560
|
+
const { cancelableAction } = this;
|
|
1561
|
+
if (cancelableAction) {
|
|
1562
|
+
await cancelableAction(error);
|
|
1563
|
+
} else if (
|
|
1564
|
+
this.isProtocolV2() &&
|
|
1565
|
+
DataManager.isBleConnect(DataManager.getSettings('env')) &&
|
|
1566
|
+
this.hasDeviceAcquire()
|
|
1567
|
+
) {
|
|
1568
|
+
await this.commands?.cancelDevice?.().catch(cancelError => {
|
|
1569
|
+
Log.debug('Protocol V2 BLE fallback cancel error', cancelError);
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1545
1572
|
await this.commands?.cancel();
|
|
1546
1573
|
|
|
1547
1574
|
if (this.runPromise) {
|
|
1548
1575
|
this.runPromise.reject(error);
|
|
1549
1576
|
this.runPromise = null;
|
|
1550
1577
|
}
|
|
1578
|
+
await cleanupPromise?.catch(() => undefined);
|
|
1551
1579
|
}
|
|
1552
1580
|
|
|
1553
1581
|
setCancelableAction(callback: (err?: Error) => Promise<unknown>) {
|
|
@@ -264,6 +264,7 @@ export class DeviceCommands {
|
|
|
264
264
|
}
|
|
265
265
|
const activeCall = this.callPromise;
|
|
266
266
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
267
|
+
let timedOut = false;
|
|
267
268
|
const cancellation = (async () => {
|
|
268
269
|
await this.dispose(true);
|
|
269
270
|
await activeCall?.catch(() => undefined);
|
|
@@ -273,11 +274,23 @@ export class DeviceCommands {
|
|
|
273
274
|
await Promise.race([
|
|
274
275
|
cancellation,
|
|
275
276
|
new Promise<void>(resolve => {
|
|
276
|
-
timer = setTimeout(
|
|
277
|
+
timer = setTimeout(() => {
|
|
278
|
+
timedOut = true;
|
|
279
|
+
resolve();
|
|
280
|
+
}, 10 * 1000);
|
|
277
281
|
}),
|
|
278
282
|
]);
|
|
279
283
|
} finally {
|
|
280
284
|
if (timer) clearTimeout(timer);
|
|
285
|
+
if (
|
|
286
|
+
timedOut &&
|
|
287
|
+
this.transport.name === 'ReactNativeBleTransport' &&
|
|
288
|
+
this.transport.disconnect
|
|
289
|
+
) {
|
|
290
|
+
await this.transport.disconnect(this.mainId).catch(error => {
|
|
291
|
+
Log.debug('BLE cancellation timeout disconnect error (ignored)', error);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
281
294
|
if (this.callPromise === activeCall) {
|
|
282
295
|
this.callPromise = undefined;
|
|
283
296
|
}
|