Skip to content

Commit c6f3b6a

Browse files
authored
fix: remove ledger heartbeat (#503)
1 parent ed75842 commit c6f3b6a

File tree

2 files changed

+0
-174
lines changed

2 files changed

+0
-174
lines changed

packages/ui/src/contexts/LedgerProvider/LedgerProvider.test.tsx

Lines changed: 0 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,117 +1187,4 @@ describe('src/contexts/LedgerProvider.tsx', () => {
11871187
});
11881188
});
11891189
});
1190-
1191-
describe('checkHeartbeat', () => {
1192-
beforeEach(() => {
1193-
// Clear any previous calls to refMock.send before each test
1194-
refMock.send.mockClear();
1195-
});
1196-
1197-
afterEach(() => {
1198-
jest.restoreAllMocks();
1199-
});
1200-
1201-
it('should not run heartbeat when transport is not available', async () => {
1202-
// Mock transportRef.current to be null
1203-
jest.spyOn(React, 'useRef').mockReturnValue({
1204-
current: null,
1205-
});
1206-
1207-
renderTestComponent();
1208-
1209-
// Fast-forward time to trigger heartbeat
1210-
jest.advanceTimersByTime(3000);
1211-
1212-
// Should not call transport.send when no transport
1213-
expect(refMock.send).not.toHaveBeenCalled();
1214-
expect(AppAvalanche).not.toHaveBeenCalled();
1215-
});
1216-
1217-
it('should run heartbeat when transport is available', async () => {
1218-
// Mock transportRef.current to be available
1219-
jest.spyOn(React, 'useRef').mockReturnValue({
1220-
current: refMock,
1221-
});
1222-
1223-
// Mock app state to exist so heartbeat runs
1224-
const mockApp = new AppAvalanche(refMock as any);
1225-
jest.spyOn(React, 'useState').mockImplementation(((initialValue: any) => {
1226-
if (initialValue === undefined) {
1227-
// This is the app state
1228-
return [mockApp, jest.fn()];
1229-
}
1230-
return [initialValue, jest.fn()];
1231-
}) as any);
1232-
1233-
renderTestComponent();
1234-
1235-
// Fast-forward time to trigger heartbeat
1236-
jest.advanceTimersByTime(3000);
1237-
1238-
// Should call transport.send when transport is available
1239-
expect(refMock.send).toHaveBeenCalled();
1240-
});
1241-
1242-
it('should run heartbeat when no app but transport is available', async () => {
1243-
// Mock transportRef.current to be available
1244-
jest.spyOn(React, 'useRef').mockReturnValue({
1245-
current: refMock,
1246-
});
1247-
1248-
// Mock app state to be undefined (no app)
1249-
jest.spyOn(React, 'useState').mockImplementation(((initialValue: any) => {
1250-
if (initialValue === undefined) {
1251-
// This is the app state - return undefined to simulate no app
1252-
return [undefined, jest.fn()];
1253-
}
1254-
return [initialValue, jest.fn()];
1255-
}) as any);
1256-
1257-
renderTestComponent();
1258-
1259-
// Fast-forward time to trigger heartbeat
1260-
jest.advanceTimersByTime(3000);
1261-
1262-
// The heartbeat should run (it will attempt to reinitialize the app)
1263-
// We can verify this by checking that the heartbeat mechanism is active
1264-
// The actual reinitialization will happen through initLedgerApp
1265-
expect(refMock.send).not.toHaveBeenCalled(); // No direct send call when no app
1266-
});
1267-
1268-
it('should detect device lock error codes correctly', () => {
1269-
// Test the error detection logic directly
1270-
const testCases = [
1271-
{ statusCode: 0x5515, shouldBeLock: true },
1272-
{ statusCode: 0x6700, shouldBeLock: true },
1273-
{ statusCode: 0x6b0c, shouldBeLock: true },
1274-
{ statusCode: 0x9001, shouldBeLock: false },
1275-
];
1276-
1277-
testCases.forEach(({ statusCode, shouldBeLock }) => {
1278-
const error = new Error('Test error') as any;
1279-
error.statusCode = statusCode;
1280-
const isLockError =
1281-
error?.statusCode === 0x5515 || // Device locked
1282-
error?.statusCode === 0x6700 || // Incorrect length
1283-
error?.statusCode === 0x6b0c; // Something went wrong
1284-
1285-
expect(isLockError).toBe(shouldBeLock);
1286-
});
1287-
});
1288-
1289-
it('should clean up interval on unmount', () => {
1290-
const clearIntervalSpy = jest.spyOn(global, 'clearInterval');
1291-
1292-
const { unmount } = renderTestComponent();
1293-
1294-
// Fast-forward to set up interval
1295-
jest.advanceTimersByTime(3000);
1296-
1297-
// Unmount component
1298-
unmount();
1299-
1300-
expect(clearIntervalSpy).toHaveBeenCalled();
1301-
});
1302-
});
13031190
});

packages/ui/src/contexts/LedgerProvider/LedgerProvider.tsx

Lines changed: 0 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,6 @@ export enum LedgerAppType {
6868
export const REQUIRED_LEDGER_VERSION = '0.7.3';
6969
export const LEDGER_VERSION_WITH_EIP_712 = '0.8.0';
7070

71-
const LEDGER_ERROR_CODES = Object.freeze({
72-
DEVICE_LOCKED: 0x5515,
73-
INCORRECT_LENGTH: 0x6700,
74-
SOMETHING_WRONG: 0x6b0c,
75-
});
76-
7771
/**
7872
* Run this here since each new window will have a different id
7973
* this is used to track the transport and close on window close
@@ -501,61 +495,6 @@ export function LedgerContextProvider({ children }: PropsWithChildren) {
501495
setLedgerVersionWarningClosed(result);
502496
}, [request]);
503497

504-
// Ledger Stax getting locked when connected via USB needs to be detected and the transport needs to be cleared
505-
// Heartbeat mechanism is being used to detect device lock
506-
useEffect(() => {
507-
let isCheckingHeartbeat = false;
508-
509-
const checkHeartbeat = async () => {
510-
if (isCheckingHeartbeat || !transportRef.current) {
511-
return;
512-
}
513-
514-
isCheckingHeartbeat = true;
515-
516-
try {
517-
if (!app) {
518-
// No app instance - try to re-establish connection
519-
await initLedgerApp(transportRef.current);
520-
} else {
521-
// Send a simple GET_VERSION command which should always require device interaction
522-
await transportRef.current.send(
523-
0xe0, // CLA - Generic command class
524-
0x01, // INS - Get version instruction
525-
0x00, // P1
526-
0x00, // P2
527-
Buffer.alloc(0), // Data
528-
[0x9000], // Expected status code for success
529-
);
530-
}
531-
} catch (error: any) {
532-
// Check if this looks like a device lock error
533-
const isLockError = Object.values(LEDGER_ERROR_CODES).includes(
534-
error?.statusCode,
535-
);
536-
537-
if (isLockError && app) {
538-
// Device appears to be locked, clearing transport but keeping heartbeat running
539-
setApp(undefined);
540-
setAppType(LedgerAppType.UNKNOWN);
541-
}
542-
} finally {
543-
isCheckingHeartbeat = false;
544-
}
545-
};
546-
547-
// Check heartbeat every 3 seconds to detect if the device is locked (3 seconds might be too excessive, but it's a good starting point to avoid false positives)
548-
const heartbeatInterval = setInterval(checkHeartbeat, 3000);
549-
550-
checkHeartbeat();
551-
552-
return () => {
553-
if (heartbeatInterval) {
554-
clearInterval(heartbeatInterval);
555-
}
556-
};
557-
}, [app, initLedgerApp]);
558-
559498
return (
560499
<LedgerContext.Provider
561500
value={{

0 commit comments

Comments
 (0)