@onekeyfe/hd-core 1.2.2-alpha.103 → 1.2.2-alpha.104
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.
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
import { HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
1
|
+
import { ERRORS, HardwareErrorCode } from '@onekeyfe/hd-shared';
|
|
2
|
+
import { DeviceSessionErrorCode } from '@onekeyfe/hd-transport';
|
|
2
3
|
|
|
4
|
+
import { DataManager } from '../src/data-manager';
|
|
5
|
+
import TransportManager from '../src/data-manager/TransportManager';
|
|
6
|
+
import { Device } from '../src/device/Device';
|
|
3
7
|
import { DeviceCommands } from '../src/device/DeviceCommands';
|
|
4
8
|
import { DEVICE } from '../src/events';
|
|
5
9
|
import { LoggerNames, getLogger } from '../src/utils';
|
|
@@ -807,6 +811,255 @@ describe('DeviceCommands Protocol V2 interactive response compatibility', () =>
|
|
|
807
811
|
});
|
|
808
812
|
});
|
|
809
813
|
|
|
814
|
+
describe('DeviceCommands Protocol V2 session busy retry', () => {
|
|
815
|
+
const busy = {
|
|
816
|
+
type: 'Failure',
|
|
817
|
+
message: {
|
|
818
|
+
code: 'Failure_ProcessError',
|
|
819
|
+
subcode: DeviceSessionErrorCode.DeviceSessionError_Busy,
|
|
820
|
+
message: 'Another flow in progress',
|
|
821
|
+
},
|
|
822
|
+
} as const;
|
|
823
|
+
const success = { type: 'Success', message: {} } as const;
|
|
824
|
+
const flushCalls = () =>
|
|
825
|
+
new Promise<void>(resolve => {
|
|
826
|
+
setImmediate(resolve);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
beforeEach(() => {
|
|
830
|
+
jest.useFakeTimers({ doNotFake: ['performance', 'setImmediate'] });
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
afterEach(() => {
|
|
834
|
+
jest.useRealTimers();
|
|
835
|
+
jest.restoreAllMocks();
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
const setup = () => {
|
|
839
|
+
const commands = createCommands();
|
|
840
|
+
commands.device.wasInterruptedByUser = jest.fn(() => false);
|
|
841
|
+
commands.mainId = 'main-id';
|
|
842
|
+
const call = jest.fn();
|
|
843
|
+
commands.transport = { call } as DeviceCommands['transport'];
|
|
844
|
+
return { commands, call };
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
it.each(['DeviceSessionGet', 'DeviceSessionAskPin', 'DeviceSessionAskPassphrase'] as const)(
|
|
848
|
+
'retries %s busy responses at 100 ms intervals without matching their message',
|
|
849
|
+
async request => {
|
|
850
|
+
const { commands, call } = setup();
|
|
851
|
+
const response =
|
|
852
|
+
request === 'DeviceSessionGet' ? { type: 'DeviceSession' as const, message: {} } : success;
|
|
853
|
+
call
|
|
854
|
+
.mockResolvedValueOnce(busy)
|
|
855
|
+
.mockResolvedValueOnce({
|
|
856
|
+
type: 'Failure',
|
|
857
|
+
message: { ...busy.message, message: 'Passphrase entry busy' },
|
|
858
|
+
})
|
|
859
|
+
.mockResolvedValueOnce({
|
|
860
|
+
type: 'Failure',
|
|
861
|
+
message: { ...busy.message, message: 'Passphrase confirm busy' },
|
|
862
|
+
})
|
|
863
|
+
.mockResolvedValue(response);
|
|
864
|
+
const payload = request === 'DeviceSessionAskPassphrase' ? { on_device: true } : {};
|
|
865
|
+
const result = commands.typedCall(request, response.type, payload).catch(error => error);
|
|
866
|
+
await flushCalls();
|
|
867
|
+
|
|
868
|
+
for (let attempt = 1; attempt <= 3; attempt += 1) {
|
|
869
|
+
jest.advanceTimersByTime(99);
|
|
870
|
+
await flushCalls();
|
|
871
|
+
expect(call).toHaveBeenCalledTimes(attempt);
|
|
872
|
+
jest.advanceTimersByTime(1);
|
|
873
|
+
await flushCalls();
|
|
874
|
+
expect(call).toHaveBeenCalledTimes(attempt + 1);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
await expect(result).resolves.toEqual(response);
|
|
878
|
+
expect(call.mock.calls).toEqual(
|
|
879
|
+
Array.from({ length: 4 }, () => [
|
|
880
|
+
commands.mainId,
|
|
881
|
+
request,
|
|
882
|
+
payload,
|
|
883
|
+
{ expectedTypes: [response.type] },
|
|
884
|
+
])
|
|
885
|
+
);
|
|
886
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
887
|
+
}
|
|
888
|
+
);
|
|
889
|
+
|
|
890
|
+
it('preserves the final busy error after three retries', async () => {
|
|
891
|
+
const { commands, call } = setup();
|
|
892
|
+
call.mockResolvedValue(busy);
|
|
893
|
+
const result = commands.typedCall('DeviceSessionGet', 'DeviceSession').catch(error => error);
|
|
894
|
+
await flushCalls();
|
|
895
|
+
for (let retry = 0; retry < 3; retry += 1) {
|
|
896
|
+
jest.advanceTimersByTime(100);
|
|
897
|
+
await flushCalls();
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
expect(call).toHaveBeenCalledTimes(4);
|
|
901
|
+
await expect(result).resolves.toMatchObject({
|
|
902
|
+
errorCode: HardwareErrorCode.DeviceBusy,
|
|
903
|
+
params: {
|
|
904
|
+
failureCode: 'Failure_ProcessError',
|
|
905
|
+
subcode: DeviceSessionErrorCode.DeviceSessionError_Busy,
|
|
906
|
+
firmwareMessage: 'Another flow in progress',
|
|
907
|
+
},
|
|
908
|
+
});
|
|
909
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
it('does not delay a successful session request', async () => {
|
|
913
|
+
const { commands, call } = setup();
|
|
914
|
+
call.mockResolvedValue(success);
|
|
915
|
+
await expect(commands.typedCall('DeviceSessionAskPin', 'Success')).resolves.toEqual(success);
|
|
916
|
+
expect(call).toHaveBeenCalledTimes(1);
|
|
917
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
it.each([
|
|
921
|
+
['DeviceSessionAskPin', 'Failure_ProcessError', 0],
|
|
922
|
+
[
|
|
923
|
+
'DeviceSessionGet',
|
|
924
|
+
'Failure_ProcessError',
|
|
925
|
+
DeviceSessionErrorCode.DeviceSessionError_InvalidSession,
|
|
926
|
+
],
|
|
927
|
+
[
|
|
928
|
+
'DeviceSessionAskPassphrase',
|
|
929
|
+
'Failure_ProcessError',
|
|
930
|
+
DeviceSessionErrorCode.DeviceSessionError_UserCancelled,
|
|
931
|
+
],
|
|
932
|
+
['DeviceSessionGet', 'Failure_DataError', DeviceSessionErrorCode.DeviceSessionError_Busy],
|
|
933
|
+
['DeviceSettingsSet', 'Failure_ProcessError', 5],
|
|
934
|
+
['EthereumSignTx', 'Failure_ProcessError', 5],
|
|
935
|
+
] as const)('does not retry %s with %s subcode %s', async (request, code, subcode) => {
|
|
936
|
+
const { commands, call } = setup();
|
|
937
|
+
call.mockResolvedValue({ type: 'Failure', message: { code, subcode, message: 'Busy' } });
|
|
938
|
+
await expect(commands.typedCall(request, 'Success')).rejects.toBeInstanceOf(Error);
|
|
939
|
+
expect(call).toHaveBeenCalledTimes(1);
|
|
940
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
it('does not retry Protocol V1 busy responses', async () => {
|
|
944
|
+
const { commands, call } = setup();
|
|
945
|
+
commands.device.isProtocolV2 = () => false;
|
|
946
|
+
call.mockResolvedValue(busy);
|
|
947
|
+
await expect(commands.typedCall('DeviceSessionGet', 'DeviceSession')).rejects.toMatchObject({
|
|
948
|
+
errorCode: HardwareErrorCode.DeviceBusy,
|
|
949
|
+
});
|
|
950
|
+
expect(call).toHaveBeenCalledTimes(1);
|
|
951
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
it('stops retrying when the next attempt fails at the transport layer', async () => {
|
|
955
|
+
const { commands, call } = setup();
|
|
956
|
+
const error = ERRORS.TypedError(HardwareErrorCode.BridgeDeviceDisconnected);
|
|
957
|
+
call.mockResolvedValueOnce(busy).mockRejectedValue(error);
|
|
958
|
+
const result = commands.typedCall('DeviceSessionGet', 'DeviceSession').catch(err => err);
|
|
959
|
+
await flushCalls();
|
|
960
|
+
jest.advanceTimersByTime(100);
|
|
961
|
+
await flushCalls();
|
|
962
|
+
await expect(result).resolves.toBe(error);
|
|
963
|
+
expect(call).toHaveBeenCalledTimes(2);
|
|
964
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
it('stops a superseded retry when a React Native run reuses the commands', async () => {
|
|
968
|
+
jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
|
|
969
|
+
const call = jest.fn().mockResolvedValueOnce(busy).mockResolvedValueOnce(busy);
|
|
970
|
+
call.mockResolvedValue(success);
|
|
971
|
+
const cancel = jest.fn();
|
|
972
|
+
jest.spyOn(TransportManager, 'getTransport').mockReturnValue({
|
|
973
|
+
call,
|
|
974
|
+
cancel,
|
|
975
|
+
} as DeviceCommands['transport']);
|
|
976
|
+
const device = Device.fromDescriptor({
|
|
977
|
+
id: 'ble-device',
|
|
978
|
+
path: 'ble-device',
|
|
979
|
+
name: 'OneKey Test',
|
|
980
|
+
debug: false,
|
|
981
|
+
commType: 'ble',
|
|
982
|
+
protocolType: 'V2',
|
|
983
|
+
});
|
|
984
|
+
const commands = new DeviceCommands(device, 'ble-device');
|
|
985
|
+
device.commands = commands;
|
|
986
|
+
const release = jest.spyOn(device, 'release');
|
|
987
|
+
const continued = jest.fn();
|
|
988
|
+
let staleCallResult: Promise<unknown> | undefined;
|
|
989
|
+
const staleRun = device
|
|
990
|
+
.run(
|
|
991
|
+
async () => {
|
|
992
|
+
const operation = commands.typedCall('DeviceSessionAskPassphrase', 'Success', {
|
|
993
|
+
on_device: true,
|
|
994
|
+
});
|
|
995
|
+
staleCallResult = operation.catch(error => error);
|
|
996
|
+
await operation;
|
|
997
|
+
continued();
|
|
998
|
+
},
|
|
999
|
+
{ keepSession: true }
|
|
1000
|
+
)
|
|
1001
|
+
.catch(error => error);
|
|
1002
|
+
await flushCalls();
|
|
1003
|
+
|
|
1004
|
+
const nextRun = device.run(
|
|
1005
|
+
async () => {
|
|
1006
|
+
await commands.typedCall('DeviceSessionAskPin', 'Success');
|
|
1007
|
+
},
|
|
1008
|
+
{ keepSession: true }
|
|
1009
|
+
);
|
|
1010
|
+
await flushCalls();
|
|
1011
|
+
expect(device.commands).toBe(commands);
|
|
1012
|
+
expect(commands.disposed).toBe(false);
|
|
1013
|
+
expect(cancel).toHaveBeenCalledTimes(1);
|
|
1014
|
+
expect(call).toHaveBeenCalledTimes(2);
|
|
1015
|
+
|
|
1016
|
+
jest.advanceTimersByTime(100);
|
|
1017
|
+
await flushCalls();
|
|
1018
|
+
|
|
1019
|
+
await expect(staleRun).resolves.toMatchObject({
|
|
1020
|
+
errorCode: HardwareErrorCode.DeviceInterruptedFromOutside,
|
|
1021
|
+
});
|
|
1022
|
+
await expect(staleCallResult).resolves.toMatchObject({
|
|
1023
|
+
errorCode: HardwareErrorCode.RuntimeError,
|
|
1024
|
+
});
|
|
1025
|
+
await expect(nextRun).resolves.toBeUndefined();
|
|
1026
|
+
expect(call.mock.calls.map(([, request]) => request)).toEqual([
|
|
1027
|
+
'DeviceSessionAskPassphrase',
|
|
1028
|
+
'DeviceSessionAskPin',
|
|
1029
|
+
'DeviceSessionAskPin',
|
|
1030
|
+
]);
|
|
1031
|
+
expect(continued).not.toHaveBeenCalled();
|
|
1032
|
+
expect(release).not.toHaveBeenCalled();
|
|
1033
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
it.each(['cancelled', 'disposed'] as const)(
|
|
1037
|
+
'does not resend when %s during backoff',
|
|
1038
|
+
async state => {
|
|
1039
|
+
const { commands, call } = setup();
|
|
1040
|
+
call.mockResolvedValueOnce(busy).mockResolvedValue(success);
|
|
1041
|
+
const result = commands.typedCall('DeviceSessionAskPin', 'Success').catch(error => error);
|
|
1042
|
+
await flushCalls();
|
|
1043
|
+
if (state === 'cancelled') {
|
|
1044
|
+
commands.device.wasInterruptedByUser = () => true;
|
|
1045
|
+
} else {
|
|
1046
|
+
commands.disposed = true;
|
|
1047
|
+
}
|
|
1048
|
+
jest.advanceTimersByTime(100);
|
|
1049
|
+
await flushCalls();
|
|
1050
|
+
|
|
1051
|
+
await expect(result).resolves.toMatchObject({
|
|
1052
|
+
errorCode:
|
|
1053
|
+
state === 'cancelled'
|
|
1054
|
+
? HardwareErrorCode.DeviceInterruptedFromUser
|
|
1055
|
+
: HardwareErrorCode.RuntimeError,
|
|
1056
|
+
});
|
|
1057
|
+
expect(call).toHaveBeenCalledTimes(1);
|
|
1058
|
+
expect(jest.getTimerCount()).toBe(0);
|
|
1059
|
+
}
|
|
1060
|
+
);
|
|
1061
|
+
});
|
|
1062
|
+
|
|
810
1063
|
describe('DeviceCommands cancellation', () => {
|
|
811
1064
|
it('stops waiting after the bounded cancellation timeout', async () => {
|
|
812
1065
|
jest.useFakeTimers({ doNotFake: ['performance'] });
|
|
@@ -62,6 +62,7 @@ export declare class DeviceCommands {
|
|
|
62
62
|
transport: Transport;
|
|
63
63
|
mainId: string;
|
|
64
64
|
disposed: boolean;
|
|
65
|
+
private disposalToken?;
|
|
65
66
|
callPromise?: Promise<DefaultMessageResponse>;
|
|
66
67
|
constructor(device: Device, mainId: string);
|
|
67
68
|
dispose(_cancelRequest: boolean): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeviceCommands.d.ts","sourceRoot":"","sources":["../../src/device/DeviceCommands.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,oBAAoB,EAG1B,MAAM,wBAAwB,CAAC;AAIhC,OAAO,EAAU,KAAK,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAQlE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAEvC,MAAM,MAAM,wBAAwB,GAAG;IACrC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,KAAK,WAAW,GAAG,QAAQ,CAAC,WAAW,CAAC;AACxC,KAAK,UAAU,GAAG,OAAO,CAAC,MAAM,WAAW,EAAE,MAAM,CAAC,CAAC;AACrD,MAAM,MAAM,oBAAoB,CAAC,CAAC,SAAS,UAAU,IAAI;IACvD,IAAI,EAAE,CAAC,CAAC;IACR,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC;CACzB,CAAC;AACF,KAAK,oBAAoB,GAAG;KACzB,CAAC,IAAI,UAAU,GAAG,oBAAoB,CAAC,CAAC,CAAC;CAC3C,CAAC;AACF,MAAM,MAAM,sBAAsB,GAAG,oBAAoB,CAAC,UAAU,CAAC,CAAC;AA8DtE,eAAO,MAAM,oBAAoB,WAAY,MAAM;;;;;;;;;;;;;;;;;;EA0ClD,CAAC;AAEF,eAAO,MAAM,0BAA0B,WAAY,MAAM;;;;;;;;;;;;;;;;;;EAgCxD,CAAC;AASF,qBAAa,cAAc;IACzB,UAAU,EAAE,MAAM,CAAC;IAEnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAE3B,MAAM,EAAE,MAAM,CAAC;IAEf,SAAS,EAAE,SAAS,CAAC;IAErB,MAAM,EAAE,MAAM,CAAC;IAEf,QAAQ,EAAE,OAAO,CAAC;IAElB,WAAW,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;gBAElC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAUpC,OAAO,CAAC,cAAc,EAAE,OAAO;
|
|
1
|
+
{"version":3,"file":"DeviceCommands.d.ts","sourceRoot":"","sources":["../../src/device/DeviceCommands.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -118,6 +118,7 @@ declare class DeviceCommands {
|
|
|
118
118
|
transport: Transport;
|
|
119
119
|
mainId: string;
|
|
120
120
|
disposed: boolean;
|
|
121
|
+
private disposalToken?;
|
|
121
122
|
callPromise?: Promise<DefaultMessageResponse>;
|
|
122
123
|
constructor(device: Device, mainId: string);
|
|
123
124
|
dispose(_cancelRequest: boolean): Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -43912,6 +43912,7 @@ class DeviceCommands {
|
|
|
43912
43912
|
var _a, _b;
|
|
43913
43913
|
return __awaiter(this, void 0, void 0, function* () {
|
|
43914
43914
|
this.disposed = true;
|
|
43915
|
+
this.disposalToken = {};
|
|
43915
43916
|
yield ((_b = (_a = this.transport).cancel) === null || _b === void 0 ? void 0 : _b.call(_a));
|
|
43916
43917
|
});
|
|
43917
43918
|
}
|
|
@@ -44085,7 +44086,24 @@ class DeviceCommands {
|
|
|
44085
44086
|
}
|
|
44086
44087
|
_commonCall(type, msg, options) {
|
|
44087
44088
|
return __awaiter(this, void 0, void 0, function* () {
|
|
44088
|
-
const
|
|
44089
|
+
const { disposalToken } = this;
|
|
44090
|
+
let resp = yield this.call(type, msg, options);
|
|
44091
|
+
for (let retry = 0; retry < 3 &&
|
|
44092
|
+
this.device.isProtocolV2() &&
|
|
44093
|
+
DEVICE_SESSION_CALLS.has(type) &&
|
|
44094
|
+
resp.type === 'Failure' &&
|
|
44095
|
+
resp.message.code === 'Failure_ProcessError' &&
|
|
44096
|
+
resp.message.subcode === hdTransport.DeviceSessionErrorCode.DeviceSessionError_Busy; retry += 1) {
|
|
44097
|
+
yield wait(100);
|
|
44098
|
+
if (this.device.wasInterruptedByUser()) {
|
|
44099
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.DeviceInterruptedFromUser);
|
|
44100
|
+
}
|
|
44101
|
+
this.checkDisposed();
|
|
44102
|
+
if (this.disposalToken !== disposalToken) {
|
|
44103
|
+
throw hdShared.ERRORS.TypedError(hdShared.HardwareErrorCode.RuntimeError, 'DeviceCommands lifecycle changed');
|
|
44104
|
+
}
|
|
44105
|
+
resp = yield this.call(type, msg, options);
|
|
44106
|
+
}
|
|
44089
44107
|
return this._filterCommonTypes(resp, type, options);
|
|
44090
44108
|
});
|
|
44091
44109
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/hd-core",
|
|
3
|
-
"version": "1.2.2-alpha.
|
|
3
|
+
"version": "1.2.2-alpha.104",
|
|
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.2-alpha.
|
|
29
|
-
"@onekeyfe/hd-transport": "1.2.2-alpha.
|
|
28
|
+
"@onekeyfe/hd-shared": "1.2.2-alpha.104",
|
|
29
|
+
"@onekeyfe/hd-transport": "1.2.2-alpha.104",
|
|
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": "67835939d9f779ffd50cd8b482d8c5b53bd580bd"
|
|
50
50
|
}
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from '@onekeyfe/hd-transport';
|
|
12
12
|
|
|
13
13
|
import TransportManager from '../data-manager/TransportManager';
|
|
14
|
-
import { LoggerNames, getLogger, patchFeatures } from '../utils';
|
|
14
|
+
import { LoggerNames, getLogger, patchFeatures, wait } from '../utils';
|
|
15
15
|
import { DEVICE, type PassphraseRequestPayload } from '../events';
|
|
16
16
|
import { DeviceModelToTypes } from '../types';
|
|
17
17
|
import {
|
|
@@ -198,6 +198,8 @@ export class DeviceCommands {
|
|
|
198
198
|
|
|
199
199
|
disposed: boolean;
|
|
200
200
|
|
|
201
|
+
private disposalToken?: object;
|
|
202
|
+
|
|
201
203
|
callPromise?: Promise<DefaultMessageResponse>;
|
|
202
204
|
|
|
203
205
|
constructor(device: Device, mainId: string) {
|
|
@@ -212,6 +214,7 @@ export class DeviceCommands {
|
|
|
212
214
|
|
|
213
215
|
async dispose(_cancelRequest: boolean) {
|
|
214
216
|
this.disposed = true;
|
|
217
|
+
this.disposalToken = {};
|
|
215
218
|
await this.transport.cancel?.();
|
|
216
219
|
}
|
|
217
220
|
|
|
@@ -439,7 +442,31 @@ export class DeviceCommands {
|
|
|
439
442
|
msg?: DefaultMessageResponse['message'],
|
|
440
443
|
options?: TransportCallOptions
|
|
441
444
|
) {
|
|
442
|
-
const
|
|
445
|
+
const { disposalToken } = this;
|
|
446
|
+
let resp = await this.call(type, msg, options);
|
|
447
|
+
// Session busy failures precede wallet derivation or reject an occupied flow.
|
|
448
|
+
// Three retries cover the 240 ms PIN dismissal animation on older V2 firmware.
|
|
449
|
+
for (
|
|
450
|
+
let retry = 0;
|
|
451
|
+
retry < 3 &&
|
|
452
|
+
this.device.isProtocolV2() &&
|
|
453
|
+
DEVICE_SESSION_CALLS.has(type) &&
|
|
454
|
+
resp.type === 'Failure' &&
|
|
455
|
+
(resp.message.code as string | FailureType) === 'Failure_ProcessError' &&
|
|
456
|
+
resp.message.subcode === DeviceSessionErrorCode.DeviceSessionError_Busy;
|
|
457
|
+
retry += 1
|
|
458
|
+
) {
|
|
459
|
+
await wait(100);
|
|
460
|
+
if (this.device.wasInterruptedByUser()) {
|
|
461
|
+
throw ERRORS.TypedError(HardwareErrorCode.DeviceInterruptedFromUser);
|
|
462
|
+
}
|
|
463
|
+
this.checkDisposed();
|
|
464
|
+
if (this.disposalToken !== disposalToken) {
|
|
465
|
+
// React Native can revive this instance. A stale retry must not release the new run.
|
|
466
|
+
throw ERRORS.TypedError(HardwareErrorCode.RuntimeError, 'DeviceCommands lifecycle changed');
|
|
467
|
+
}
|
|
468
|
+
resp = await this.call(type, msg, options);
|
|
469
|
+
}
|
|
443
470
|
return this._filterCommonTypes(resp, type, options);
|
|
444
471
|
}
|
|
445
472
|
|