0
# Error Handling
1
2
Comprehensive error classes and patterns for handling transport-specific and device status errors. The @ledgerhq/hw-transport package provides detailed error information with specific error codes, status messages, and recovery strategies.
3
4
## Capabilities
5
6
### Transport Errors
7
8
Generic transport errors that occur during communication setup, data exchange, or connection management.
9
10
```typescript { .api }
11
/**
12
* TransportError is used for any generic transport errors
13
* e.g. Error thrown when data received by exchanges are incorrect or if exchange failed to communicate with the device for various reasons
14
*/
15
class TransportError extends Error {
16
constructor(message: string, id: string);
17
name: "TransportError";
18
message: string;
19
id: string;
20
stack: string;
21
}
22
```
23
24
**Usage Example:**
25
26
```javascript
27
import { TransportError } from "@ledgerhq/hw-transport";
28
29
try {
30
const transport = await MyTransport.create();
31
const largeData = Buffer.alloc(300); // Too large for APDU
32
await transport.send(0xB0, 0x01, 0x00, 0x00, largeData);
33
34
} catch (error) {
35
if (error instanceof TransportError) {
36
console.log("Transport error ID:", error.id); // "DataLengthTooBig"
37
console.log("Error message:", error.message); // "data.length exceed 256 bytes limit. Got: 300"
38
39
// Handle specific transport errors
40
switch (error.id) {
41
case "DataLengthTooBig":
42
console.log("Split data into smaller chunks");
43
break;
44
case "NoDeviceFound":
45
console.log("Please connect your Ledger device");
46
break;
47
case "ListenTimeout":
48
console.log("Timeout waiting for device - please retry");
49
break;
50
default:
51
console.log("Unknown transport error:", error.id);
52
}
53
}
54
}
55
```
56
57
### Device Status Errors
58
59
Errors returned by the Ledger device as status codes, indicating specific device states or command failures.
60
61
```typescript { .api }
62
/**
63
* Error thrown when a device returned a non-success status
64
* The error.statusCode is one of the StatusCodes exported by this library
65
*/
66
class TransportStatusError extends Error {
67
constructor(statusCode: number);
68
name: "TransportStatusError";
69
message: string;
70
statusCode: number;
71
statusText: string;
72
stack: string;
73
}
74
```
75
76
### Race Condition Errors
77
78
Errors that occur when multiple operations attempt to use the transport simultaneously, violating the atomic operation requirement.
79
80
```javascript { .api }
81
/**
82
* Error thrown when an operation is attempted while another operation is in progress
83
* Prevents race conditions by enforcing atomic operations
84
*/
85
class TransportRaceCondition extends Error {
86
constructor(message: string);
87
name: "TransportRaceCondition";
88
message: string;
89
stack: string;
90
}
91
```
92
93
**Usage Example:**
94
95
```javascript
96
import { TransportRaceCondition } from "@ledgerhq/hw-transport";
97
98
// Example of race condition prevention
99
async function performOperation(transport) {
100
try {
101
// Start first operation
102
const operation1 = transport.send(0xB0, 0x01, 0x00, 0x00);
103
104
// Attempt second operation while first is running - this will throw
105
const operation2 = transport.send(0xB0, 0x02, 0x00, 0x00);
106
107
} catch (error) {
108
if (error instanceof TransportRaceCondition) {
109
console.log("Race condition detected:", error.message);
110
// "An action was already pending on the Ledger device. Please deny or reconnect."
111
112
// Wait for first operation to complete, then retry
113
await new Promise(resolve => setTimeout(resolve, 1000));
114
return performOperation(transport);
115
}
116
}
117
}
118
```
119
120
**Transport Status Error Usage:**
121
122
```javascript
123
import { TransportStatusError, StatusCodes } from "@ledgerhq/hw-transport";
124
125
try {
126
// Attempt to sign a transaction
127
const signature = await transport.send(0xE0, 0x04, 0x01, 0x00, transactionData);
128
129
} catch (error) {
130
if (error instanceof TransportStatusError) {
131
console.log("Status code:", "0x" + error.statusCode.toString(16));
132
console.log("Status text:", error.statusText);
133
console.log("Message:", error.message);
134
135
// Handle specific device status codes
136
switch (error.statusCode) {
137
case StatusCodes.CONDITIONS_OF_USE_NOT_SATISFIED:
138
console.log("User rejected the transaction");
139
showUserMessage("Transaction cancelled by user");
140
break;
141
142
case StatusCodes.SECURITY_STATUS_NOT_SATISFIED:
143
console.log("Device is locked or access denied");
144
showUserMessage("Please unlock your device and try again");
145
break;
146
147
case StatusCodes.INCORRECT_DATA:
148
console.log("Invalid transaction data");
149
showUserMessage("Transaction data is invalid");
150
break;
151
152
case StatusCodes.INS_NOT_SUPPORTED:
153
console.log("Command not supported by this app");
154
showUserMessage("Please open the correct app on your device");
155
break;
156
157
default:
158
console.log("Unknown device error:", error.statusText);
159
showUserMessage("Device error: " + error.message);
160
}
161
}
162
}
163
```
164
165
### Status Codes
166
167
Comprehensive set of status codes returned by Ledger devices, following ISO 7816-4 standards.
168
169
```typescript { .api }
170
interface StatusCodes {
171
// Success
172
OK: 0x9000;
173
174
// Parameter errors
175
INCORRECT_LENGTH: 0x6700;
176
MISSING_CRITICAL_PARAMETER: 0x6800;
177
INCORRECT_DATA: 0x6A80;
178
INCORRECT_P1_P2: 0x6B00;
179
180
// Security errors
181
SECURITY_STATUS_NOT_SATISFIED: 0x6982;
182
CONDITIONS_OF_USE_NOT_SATISFIED: 0x6985;
183
184
// Command errors
185
INS_NOT_SUPPORTED: 0x6D00;
186
CLA_NOT_SUPPORTED: 0x6E00;
187
188
// System errors
189
TECHNICAL_PROBLEM: 0x6F00;
190
NOT_ENOUGH_MEMORY_SPACE: 0x6A84;
191
192
// Authentication errors
193
PIN_REMAINING_ATTEMPTS: 0x63C0;
194
CODE_BLOCKED: 0x9840;
195
196
// And many more...
197
}
198
```
199
200
### Status Message Helper
201
202
Utility function to get human-readable messages for status codes.
203
204
```typescript { .api }
205
/**
206
* Get alternative status message for common error codes
207
* @param code Status code number
208
* @returns Human-readable error message or undefined
209
*/
210
function getAltStatusMessage(code: number): string | undefined | null;
211
```
212
213
**Usage Example:**
214
215
```javascript
216
import { getAltStatusMessage, StatusCodes } from "@ledgerhq/hw-transport";
217
218
// Get user-friendly messages
219
console.log(getAltStatusMessage(0x6700)); // "Incorrect length"
220
console.log(getAltStatusMessage(0x6982)); // "Security not satisfied (dongle locked or have invalid access rights)"
221
console.log(getAltStatusMessage(0x6985)); // "Condition of use not satisfied (denied by the user?)"
222
223
// Use in error handling
224
function handleStatusError(statusCode) {
225
const friendlyMessage = getAltStatusMessage(statusCode);
226
if (friendlyMessage) {
227
console.log("User-friendly error:", friendlyMessage);
228
} else {
229
console.log("Status code:", "0x" + statusCode.toString(16));
230
}
231
}
232
```
233
234
## Common Error Patterns
235
236
### Connection and Discovery Errors
237
238
```javascript
239
// Device not found
240
try {
241
const transport = await MyTransport.create();
242
} catch (error) {
243
if (error instanceof TransportError && error.id === "NoDeviceFound") {
244
console.log("No Ledger device found. Please:");
245
console.log("1. Connect your device");
246
console.log("2. Make sure it's unlocked");
247
console.log("3. Open the correct app");
248
}
249
}
250
251
// Platform not supported
252
try {
253
const isSupported = await MyTransport.isSupported();
254
if (!isSupported) {
255
throw new Error("Transport not supported");
256
}
257
} catch (error) {
258
console.log("This browser/platform doesn't support Ledger connectivity");
259
console.log("Please try Chrome, Firefox, or use the desktop app");
260
}
261
```
262
263
### User Interaction Errors
264
265
```javascript
266
async function handleUserRejection(operation) {
267
try {
268
return await operation();
269
} catch (error) {
270
if (error instanceof TransportStatusError) {
271
switch (error.statusCode) {
272
case StatusCodes.CONDITIONS_OF_USE_NOT_SATISFIED:
273
return { rejected: true, reason: "user_cancelled" };
274
275
case StatusCodes.SECURITY_STATUS_NOT_SATISFIED:
276
return { rejected: true, reason: "device_locked" };
277
278
default:
279
throw error; // Re-throw other status errors
280
}
281
}
282
throw error; // Re-throw non-status errors
283
}
284
}
285
286
// Usage
287
const result = await handleUserRejection(async () => {
288
return await transport.send(0xE0, 0x04, 0x01, 0x00, txData);
289
});
290
291
if (result.rejected) {
292
if (result.reason === "user_cancelled") {
293
showMessage("Transaction cancelled by user");
294
} else if (result.reason === "device_locked") {
295
showMessage("Please unlock your device and try again");
296
}
297
} else {
298
showMessage("Transaction signed successfully");
299
}
300
```
301
302
### Data Validation Errors
303
304
```javascript
305
function validateAndSend(transport, data) {
306
// Pre-validate data size
307
if (data.length >= 256) {
308
throw new TransportError(
309
`Data too large: ${data.length} bytes (max 255)`,
310
"DataLengthTooBig"
311
);
312
}
313
314
// Pre-validate data format
315
if (!Buffer.isBuffer(data)) {
316
throw new TransportError(
317
"Data must be a Buffer",
318
"InvalidDataType"
319
);
320
}
321
322
return transport.send(0xB0, 0x01, 0x00, 0x00, data);
323
}
324
```
325
326
### Race Condition Handling
327
328
```javascript
329
import { TransportRaceCondition } from "@ledgerhq/errors";
330
331
async function safeExchange(transport, operation) {
332
try {
333
return await operation();
334
} catch (error) {
335
if (error instanceof TransportRaceCondition) {
336
console.log("Another operation is in progress");
337
338
// Wait and retry
339
await new Promise(resolve => setTimeout(resolve, 1000));
340
return await operation();
341
}
342
throw error;
343
}
344
}
345
```
346
347
## Error Recovery Strategies
348
349
### Automatic Retry with Backoff
350
351
```javascript
352
async function withRetry(operation, maxRetries = 3) {
353
let lastError;
354
355
for (let attempt = 1; attempt <= maxRetries; attempt++) {
356
try {
357
return await operation();
358
} catch (error) {
359
lastError = error;
360
361
// Determine if error is retryable
362
const retryableErrors = [
363
"TransportRaceCondition",
364
"DisconnectedDevice",
365
"TransportInterfaceNotAvailable"
366
];
367
368
if (error instanceof TransportError &&
369
retryableErrors.includes(error.id)) {
370
371
console.log(`Attempt ${attempt} failed: ${error.id}, retrying...`);
372
373
// Exponential backoff
374
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
375
await new Promise(resolve => setTimeout(resolve, delay));
376
377
} else {
378
// Non-retryable error
379
throw error;
380
}
381
}
382
}
383
384
throw lastError;
385
}
386
387
// Usage
388
const result = await withRetry(async () => {
389
const transport = await MyTransport.create();
390
try {
391
return await transport.send(0xB0, 0x01, 0x00, 0x00);
392
} finally {
393
await transport.close();
394
}
395
});
396
```
397
398
### Error Context Enhancement
399
400
```javascript
401
class EnhancedTransportError extends Error {
402
constructor(originalError, context) {
403
super(originalError.message);
404
this.name = "EnhancedTransportError";
405
this.originalError = originalError;
406
this.context = context;
407
this.timestamp = new Date().toISOString();
408
}
409
}
410
411
async function enhancedOperation(transport, operationName, operation) {
412
try {
413
return await operation();
414
} catch (error) {
415
const context = {
416
operation: operationName,
417
deviceModel: transport.deviceModel?.id,
418
exchangeTimeout: transport.exchangeTimeout,
419
timestamp: new Date().toISOString()
420
};
421
422
throw new EnhancedTransportError(error, context);
423
}
424
}
425
```
426
427
### User-Friendly Error Messages
428
429
```javascript
430
function getUserFriendlyErrorMessage(error) {
431
if (error instanceof TransportStatusError) {
432
const messages = {
433
[StatusCodes.CONDITIONS_OF_USE_NOT_SATISFIED]:
434
"Please approve the action on your Ledger device",
435
[StatusCodes.SECURITY_STATUS_NOT_SATISFIED]:
436
"Please unlock your Ledger device",
437
[StatusCodes.INS_NOT_SUPPORTED]:
438
"Please open the correct app on your Ledger device",
439
[StatusCodes.INCORRECT_DATA]:
440
"Invalid data format - please check your input",
441
[StatusCodes.NOT_ENOUGH_MEMORY_SPACE]:
442
"Not enough space on your Ledger device"
443
};
444
445
return messages[error.statusCode] ||
446
`Device error: ${error.statusText}`;
447
}
448
449
if (error instanceof TransportError) {
450
const messages = {
451
"NoDeviceFound": "Please connect and unlock your Ledger device",
452
"ListenTimeout": "Device not found - please check connection",
453
"DataLengthTooBig": "Data too large - please contact support",
454
"TransportLocked": "Please wait for current operation to complete"
455
};
456
457
return messages[error.id] || `Connection error: ${error.message}`;
458
}
459
460
return "An unexpected error occurred. Please try again.";
461
}
462
```