@onekeyfe/hd-core 1.2.2-alpha.8 → 1.2.3-alpha.1

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.
Files changed (40) hide show
  1. package/__tests__/DeviceCommands.test.ts +254 -1
  2. package/__tests__/device-lifecycle-events.test.ts +83 -14
  3. package/__tests__/logBlockEvent.test.ts +45 -122
  4. package/__tests__/open-wallet-session.test.ts +139 -5
  5. package/__tests__/protocol-v2.test.ts +81 -2
  6. package/__tests__/sol-sign-offchain-message.test.ts +72 -0
  7. package/dist/api/FirmwareUpdateV4.d.ts +1 -0
  8. package/dist/api/FirmwareUpdateV4.d.ts.map +1 -1
  9. package/dist/api/UploadPortfolio.d.ts +1 -0
  10. package/dist/api/UploadPortfolio.d.ts.map +1 -1
  11. package/dist/api/solana/SolSignOffchainMessage.d.ts.map +1 -1
  12. package/dist/core/RequestQueue.d.ts +2 -0
  13. package/dist/core/RequestQueue.d.ts.map +1 -1
  14. package/dist/core/index.d.ts +2 -1
  15. package/dist/core/index.d.ts.map +1 -1
  16. package/dist/core/uiPromiseRegistry.d.ts +1 -1
  17. package/dist/device/DeviceCommands.d.ts +5 -4
  18. package/dist/device/DeviceCommands.d.ts.map +1 -1
  19. package/dist/events/logBlockEvent.d.ts.map +1 -1
  20. package/dist/index.d.ts +19 -12
  21. package/dist/index.js +171 -141
  22. package/dist/protocols/protocol-v2/walletSession.d.ts.map +1 -1
  23. package/dist/types/api/protocolV2.d.ts +3 -4
  24. package/dist/types/api/protocolV2.d.ts.map +1 -1
  25. package/dist/types/api/solSignOffchainMessage.d.ts +1 -0
  26. package/dist/types/api/solSignOffchainMessage.d.ts.map +1 -1
  27. package/dist/utils/patch.d.ts +1 -1
  28. package/package.json +4 -4
  29. package/src/api/FirmwareUpdateV4.ts +9 -4
  30. package/src/api/UploadPortfolio.ts +5 -4
  31. package/src/api/solana/SolSignOffchainMessage.ts +44 -4
  32. package/src/core/RequestQueue.ts +16 -1
  33. package/src/core/index.ts +43 -20
  34. package/src/data/messages/messages-protocol-v2.json +28 -33
  35. package/src/data/messages/messages.json +8 -5
  36. package/src/device/DeviceCommands.ts +29 -2
  37. package/src/events/logBlockEvent.ts +6 -75
  38. package/src/protocols/protocol-v2/walletSession.ts +11 -2
  39. package/src/types/api/protocolV2.ts +3 -4
  40. package/src/types/api/solSignOffchainMessage.ts +2 -0
@@ -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'] });
@@ -2,12 +2,14 @@ import { EDeviceType, ERRORS, HardwareErrorCode, createDeferred } from '@onekeyf
2
2
  import { DeviceType, TRANSPORT_EVENT } from '@onekeyfe/hd-transport';
3
3
 
4
4
  import {
5
+ cancel,
5
6
  initConnector,
6
7
  initCore,
8
+ isDeviceIdentityMismatchError,
7
9
  isMissingDetectedProtocolV2Error,
8
- isProtocolV2PeerRemovedPairingError,
9
10
  isRetryableBleConnectionError,
10
11
  isRetryableBleProtocolV2ProbeError,
12
+ isTerminalBleStaleBondError,
11
13
  resolveBleConnectProtocol,
12
14
  } from '../src/core';
13
15
  import { DataManager } from '../src/data-manager';
@@ -120,6 +122,58 @@ describe('public device lifecycle events', () => {
120
122
  expect(context.getPrePendingCallPromise('device-a')).toBe(replacementCleanup.promise);
121
123
  });
122
124
 
125
+ test('cancels only requests associated with the requested connect id', () => {
126
+ core = initCore();
127
+ const context = (core as any).getCoreContext();
128
+ const deviceA = {
129
+ mainId: 'transport-session-a',
130
+ getConnectId: jest.fn(() => 'serial-a'),
131
+ interruptionFromUser: jest.fn().mockResolvedValue(undefined),
132
+ };
133
+ const deviceB = {
134
+ mainId: 'device-b',
135
+ getConnectId: jest.fn(() => 'serial-b'),
136
+ interruptionFromUser: jest.fn().mockResolvedValue(undefined),
137
+ };
138
+ const taskA = context.requestQueue.createTask({
139
+ responseID: 101,
140
+ connectId: '',
141
+ device: deviceA,
142
+ } as never);
143
+ const taskB = context.requestQueue.createTask({
144
+ responseID: 102,
145
+ connectId: 'device-b',
146
+ device: deviceB,
147
+ } as never);
148
+ const signalA = taskA.abortController?.signal;
149
+ const signalB = taskB.abortController?.signal;
150
+
151
+ cancel(context, 'serial-a');
152
+
153
+ expect(signalA?.aborted).toBe(true);
154
+ expect(context.requestQueue.getTask(taskA.id)).toBeUndefined();
155
+ expect(signalB?.aborted).toBe(false);
156
+ expect(context.requestQueue.getTask(taskB.id)).toBe(taskB);
157
+ expect(deviceA.interruptionFromUser).toHaveBeenCalledTimes(1);
158
+ expect(deviceB.interruptionFromUser).not.toHaveBeenCalled();
159
+ });
160
+
161
+ test('aborts every request before cancel-all rejects WebUSB tasks', () => {
162
+ jest.spyOn(DataManager, 'getSettings').mockReturnValue('webusb' as never);
163
+ core = initCore();
164
+ const context = (core as any).getCoreContext();
165
+ const task = context.requestQueue.createTask({
166
+ responseID: 103,
167
+ connectId: 'webusb-device',
168
+ } as never);
169
+ const signal = task.abortController?.signal;
170
+
171
+ cancel(context);
172
+
173
+ expect(signal?.aborted).toBe(true);
174
+ expect(context.requestQueue.getTask(task.id)).toBeUndefined();
175
+ });
176
+
123
177
  test('keeps shared device lifecycle listeners across a device cache reset', () => {
124
178
  jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
125
179
  core = initCore();
@@ -314,6 +368,7 @@ describe('public device lifecycle events', () => {
314
368
  [HardwareErrorCode.PollingTimeout, false],
315
369
  [HardwareErrorCode.BleDeviceBondError, false],
316
370
  [HardwareErrorCode.BlePeerRemovedPairingInformation, false],
371
+ [HardwareErrorCode.BleBondInvalid, false],
317
372
  ] as const)('retries a BLE connection error with error code %s: %s', (errorCode, expected) => {
318
373
  const method = { payload: { connectProtocol: 'V2' } } as never;
319
374
  const error = {
@@ -324,21 +379,35 @@ describe('public device lifecycle events', () => {
324
379
  expect(isRetryableBleConnectionError(method, error)).toBe(expected);
325
380
  });
326
381
 
382
+ test('does not retry the desktop acquire deadline fallback', () => {
383
+ const method = { payload: { connectProtocol: 'V2' } } as never;
384
+ const error = {
385
+ errorCode: HardwareErrorCode.BleTimeoutError,
386
+ params: { acquireDeadlineExceeded: true },
387
+ };
388
+
389
+ expect(isRetryableBleConnectionError(method, error)).toBe(false);
390
+ });
391
+
327
392
  test.each([
328
- ['V2', true],
329
- ['V1', false],
330
- [undefined, false],
331
- ] as const)(
332
- 'treats peer-removed pairing as terminal only for Protocol %s: %s',
333
- (connectProtocol, expected) => {
334
- const method = { payload: { connectProtocol } } as never;
335
- const error = {
336
- errorCode: HardwareErrorCode.BlePeerRemovedPairingInformation,
337
- };
393
+ [HardwareErrorCode.BleDeviceBondError, true],
394
+ [HardwareErrorCode.BlePeerRemovedPairingInformation, true],
395
+ [HardwareErrorCode.BleBondInvalid, true],
396
+ [HardwareErrorCode.DeviceNotFound, false],
397
+ ] as const)('treats BLE stale-bond error code %s as terminal: %s', (errorCode, expected) => {
398
+ const error = {
399
+ errorCode,
400
+ };
338
401
 
339
- expect(isProtocolV2PeerRemovedPairingError(method, error)).toBe(expected);
340
- }
341
- );
402
+ expect(isTerminalBleStaleBondError(error)).toBe(expected);
403
+ });
404
+
405
+ test.each([
406
+ [HardwareErrorCode.DeviceCheckDeviceIdError, true],
407
+ [HardwareErrorCode.DeviceNotFound, false],
408
+ ] as const)('treats device identity error code %s as a mismatch: %s', (errorCode, expected) => {
409
+ expect(isDeviceIdentityMismatchError({ errorCode })).toBe(expected);
410
+ });
342
411
 
343
412
  test('converts an internal transport disconnect into a public KnownDevice snapshot', () => {
344
413
  jest.spyOn(DataManager, 'getSettings').mockReturnValue('react-native' as never);
@@ -7,36 +7,6 @@ import {
7
7
  } from '../src/events';
8
8
 
9
9
  describe('getLogBlockLabel', () => {
10
- it('blocks evmSignTypedData params before logging large typed data', () => {
11
- expect(
12
- getLogBlockLabel({
13
- method: 'evmSignTypedData',
14
- data: {
15
- message: {
16
- data: `0x${'ab'.repeat(4096)}`,
17
- },
18
- },
19
- })
20
- ).toBe('evmSignTypedData');
21
- });
22
-
23
- it('blocks evmSignTypedData iframe call payload before bridge logging', () => {
24
- expect(
25
- getLogBlockLabel({
26
- event: 'iframe-call',
27
- type: 'iframe-call',
28
- payload: {
29
- method: 'evmSignTypedData',
30
- data: {
31
- message: {
32
- data: `0x${'ab'.repeat(4096)}`,
33
- },
34
- },
35
- },
36
- })
37
- ).toBe('evmSignTypedData');
38
- });
39
-
40
10
  it('keeps existing sensitive UI response blocking', () => {
41
11
  expect(getLogBlockLabel({ type: UI_RESPONSE.RECEIVE_PIN })).toBe(UI_RESPONSE.RECEIVE_PIN);
42
12
  });
@@ -53,119 +23,72 @@ describe('getLogBlockLabel', () => {
53
23
  },
54
24
  })
55
25
  ).toBe(type);
56
- }
57
- );
58
-
59
- it('blocks openWalletSession wallet identifiers in direct and iframe call logging', () => {
60
- const payload = {
61
- method: 'openWalletSession',
62
- mode: 'resume-hidden',
63
- deviceId: 'device-id',
64
- passphraseState: 'wallet-identifier',
65
- };
66
-
67
- expect(getLogBlockLabel(payload)).toBe('openWalletSession');
68
- expect(
69
- getLogBlockLabel({
70
- event: 'iframe-call',
71
- type: 'iframe-call',
72
- payload,
73
- })
74
- ).toBe('openWalletSession');
75
- });
76
-
77
- it('keeps openWalletSession metadata while redacting wallet session identifiers', () => {
78
- const safeResponse = getSafeLogPayload(
79
- {
80
- success: true,
81
- payload: {
82
- protocol: 'V2',
83
- walletType: 'hidden',
84
- deviceId: 'device-id',
85
- passphraseState: 'wallet-identifier',
86
- sessionId: 'wallet-session-id',
87
- resumed: false,
88
- },
89
- },
90
- 'openWalletSession'
91
- );
92
-
93
- expect(safeResponse).toEqual({
94
- method: 'openWalletSession',
95
- success: true,
96
- payload: {
97
- protocol: 'V2',
98
- walletType: 'hidden',
99
- deviceId: 'device-id',
100
- passphraseState: '[REDACTED]',
101
- sessionId: '[REDACTED]',
102
- resumed: false,
103
- },
104
- });
105
- });
106
-
107
- it.each(['deviceUploadNft', 'deviceUploadWallpaper', 'uploadPortfolio'])(
108
- 'skips large Base64 resource payload logging for %s',
109
- method => {
110
- const request = { method, path: 'resource.bin', data: 'A'.repeat(1024 * 1024) };
111
- expect(getLogBlockLabel(request)).toBe(method);
112
- expect(getSafeLogPayload(request, method)).toEqual({
113
- method,
26
+ expect(
27
+ getSafeLogPayload(
28
+ {
29
+ type,
30
+ payload: {
31
+ passphraseState: 'wallet-identifier',
32
+ },
33
+ },
34
+ type
35
+ )
36
+ ).toEqual({
37
+ method: type,
114
38
  payload: '[REDACTED]',
115
39
  });
116
40
  }
117
41
  );
118
42
 
119
- it.each(['fileWrite', 'fileRead'])(
120
- 'keeps metadata and replaces binary payloads with their size for %s',
43
+ it.each(['deviceUploadNft', 'deviceUploadWallpaper', 'uploadPortfolio', 'fileWrite', 'fileRead'])(
44
+ 'skips large resource or binary payload logging for %s',
121
45
  method => {
122
- const request = { method, path: 'resource.bin', data: new Uint8Array(1024) };
46
+ const request = { method, path: 'resource.bin', data: 'A'.repeat(1024) };
123
47
  expect(getLogBlockLabel(request)).toBe(method);
124
48
  expect(getSafeLogPayload(request, method)).toEqual({
125
49
  method,
126
- path: 'resource.bin',
127
- data: '[BINARY:1024]',
50
+ payload: '[REDACTED]',
128
51
  });
129
52
  }
130
53
  );
131
54
 
132
- it.each(['evmSignMessage', 'btcSignMessage', 'evmSignTransaction'])(
133
- 'blocks request and response payload logging for signing method %s',
134
- method => {
135
- expect(getLogBlockLabel({ method, message: 'sensitive signing payload' })).toBe(method);
136
- expect(
137
- getLogBlockLabel({
138
- event: 'iframe-call',
139
- payload: { method, message: 'sensitive signing payload' },
140
- })
141
- ).toBe(method);
142
- }
143
- );
55
+ it('logs signing requests and responses as-is', () => {
56
+ const request = {
57
+ method: 'btcSignMessage',
58
+ path: "m/44'/0'/0'/0/0",
59
+ messageHex: '68656c6c6f',
60
+ coin: 'Bitcoin',
61
+ };
62
+ const iframeRequest = {
63
+ event: 'iframe-call',
64
+ payload: request,
65
+ };
66
+ const response = {
67
+ success: true,
68
+ payload: {
69
+ signature: 'signature-bytes',
70
+ address: 'bc1qexample',
71
+ },
72
+ };
73
+
74
+ expect(getLogBlockLabel(request)).toBeUndefined();
75
+ expect(getLogBlockLabel(iframeRequest)).toBeUndefined();
76
+ expect(getSafeLogPayload(request)).toBe(request);
77
+ expect(getSafeLogPayload(response)).toBe(response);
78
+ });
144
79
 
145
80
  it('keeps ordinary API requests and responses visible', () => {
146
81
  const request = { method: 'getDeviceState', connectId: 'connect-id', scope: 'runtime' };
147
82
  const response = { success: true, payload: { protocol: 'V2', initialized: true } };
148
83
 
149
84
  expect(getLogBlockLabel(request)).toBeUndefined();
150
- expect(getSafeLogPayload(request)).toEqual(request);
151
- expect(getSafeLogPayload(response)).toEqual(response);
152
- });
153
-
154
- it('redacts sensitive keys even for ordinary API payloads', () => {
155
- expect(
156
- getSafeLogPayload({
157
- method: 'ordinaryMethod',
158
- payload: { session_id: 'session-secret', nested: { pin: '1234' } },
159
- })
160
- ).toEqual({
161
- method: 'ordinaryMethod',
162
- payload: { session_id: '[REDACTED]', nested: { pin: '[REDACTED]' } },
163
- });
85
+ expect(getSafeLogPayload(request)).toBe(request);
86
+ expect(getSafeLogPayload(response)).toBe(response);
164
87
  });
165
88
 
166
- it('puts sensitive API method names in the log label', () => {
167
- expect(formatLogMethodLabel('response:', 'openWalletSession')).toBe(
168
- 'response: [openWalletSession]'
89
+ it('puts blocked method names in the log label', () => {
90
+ expect(formatLogMethodLabel('response:', 'deviceUploadNft')).toBe(
91
+ 'response: [deviceUploadNft]'
169
92
  );
170
93
  expect(formatLogMethodLabel('response:')).toBe('response:');
171
94
  });