Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/hw-wallet-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Add hardware related error mappings and custom hardware error ([#421](https://github.com/MetaMask/accounts/pull/421))
- Add BLE and mobile error mappings with new error codes for Bluetooth permissions, connection states, and mobile support ([#433](https://github.com/MetaMask/accounts/pull/433))

[Unreleased]: https://github.com/MetaMask/accounts/
128 changes: 127 additions & 1 deletion packages/hw-wallet-sdk/src/hardware-error-mappings.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { LEDGER_ERROR_MAPPINGS } from './hardware-error-mappings';
import {
LEDGER_ERROR_MAPPINGS,
BLE_ERROR_MAPPINGS,
MOBILE_ERROR_MAPPINGS,
} from './hardware-error-mappings';
import { ErrorCode, Severity, Category } from './hardware-errors-enums';

describe('HARDWARE_ERROR_MAPPINGS', () => {
Expand Down Expand Up @@ -102,4 +106,126 @@ describe('HARDWARE_ERROR_MAPPINGS', () => {
});
});
});

describe('BLE mappings', () => {
const errorMappings = BLE_ERROR_MAPPINGS;

it('has errorMappings object', () => {
expect(errorMappings).toBeDefined();
expect(typeof errorMappings).toBe('object');
});

describe('permission errors', () => {
it('maps BLUETOOTH_PERMISSION_DENIED correctly', () => {
const mapping = errorMappings.BLUETOOTH_PERMISSION_DENIED;
expect(mapping.code).toBe(ErrorCode.PermissionBluetoothDenied);
expect(mapping.severity).toBe(Severity.Err);
expect(mapping.category).toBe(Category.Configuration);
expect(mapping.userActionable).toBe(true);
expect(mapping.retryable).toBe(false);
});

it('maps LOCATION_PERMISSION_DENIED correctly', () => {
const mapping = errorMappings.LOCATION_PERMISSION_DENIED;
expect(mapping.code).toBe(ErrorCode.PermissionLocationDenied);
expect(mapping.severity).toBe(Severity.Err);
expect(mapping.category).toBe(Category.Configuration);
expect(mapping.userMessage).toContain('Location');
});

it('maps NEARBY_DEVICES_PERMISSION_DENIED correctly', () => {
const mapping = errorMappings.NEARBY_DEVICES_PERMISSION_DENIED;
expect(mapping.code).toBe(ErrorCode.PermissionNearbyDenied);
expect(mapping.severity).toBe(Severity.Err);
expect(mapping.category).toBe(Category.Configuration);
});
});

describe('bluetooth state errors', () => {
it('maps BLUETOOTH_DISABLED correctly', () => {
const mapping = errorMappings.BLUETOOTH_DISABLED;
expect(mapping.code).toBe(ErrorCode.BluetoothDisabled);
expect(mapping.severity).toBe(Severity.Warning);
expect(mapping.category).toBe(Category.Connection);
expect(mapping.userActionable).toBe(true);
expect(mapping.retryable).toBe(true);
});

it('maps BLUETOOTH_SCAN_FAILED correctly', () => {
const mapping = errorMappings.BLUETOOTH_SCAN_FAILED;
expect(mapping.code).toBe(ErrorCode.BluetoothScanFailed);
expect(mapping.severity).toBe(Severity.Err);
expect(mapping.category).toBe(Category.Connection);
expect(mapping.retryable).toBe(true);
});

it('maps BLUETOOTH_CONNECTION_FAILED correctly', () => {
const mapping = errorMappings.BLUETOOTH_CONNECTION_FAILED;
expect(mapping.code).toBe(ErrorCode.BluetoothConnectionFailed);
expect(mapping.severity).toBe(Severity.Err);
expect(mapping.category).toBe(Category.Connection);
expect(mapping.retryable).toBe(true);
});
});

it('has valid structure for all mappings', () => {
Object.entries(errorMappings).forEach(([_, mapping]) => {
expect(mapping).toHaveProperty('code');
expect(mapping).toHaveProperty('message');
expect(mapping).toHaveProperty('severity');
expect(mapping).toHaveProperty('category');
expect(mapping).toHaveProperty('userActionable');
expect(mapping).toHaveProperty('retryable');

const numericErrorCodes = Object.values(ErrorCode).filter(
(value): value is number => typeof value === 'number',
);
expect(numericErrorCodes).toContain(mapping.code);
expect(Object.values(Severity)).toContain(mapping.severity);
expect(Object.values(Category)).toContain(mapping.category);
expect(typeof mapping.message).toBe('string');
expect(typeof mapping.userActionable).toBe('boolean');
expect(typeof mapping.retryable).toBe('boolean');
});
});
});

describe('Mobile mappings', () => {
const errorMappings = MOBILE_ERROR_MAPPINGS;

it('has errorMappings object', () => {
expect(errorMappings).toBeDefined();
expect(typeof errorMappings).toBe('object');
});

it('maps NOT_SUPPORTED correctly', () => {
const mapping = errorMappings.NOT_SUPPORTED;
expect(mapping.code).toBe(ErrorCode.MobileNotSupported);
expect(mapping.severity).toBe(Severity.Err);
expect(mapping.category).toBe(Category.DeviceState);
expect(mapping.userActionable).toBe(false);
expect(mapping.retryable).toBe(false);
});

it('has valid structure for all mappings', () => {
Object.entries(errorMappings).forEach(([_, mapping]) => {
expect(mapping).toHaveProperty('code');
expect(mapping).toHaveProperty('message');
expect(mapping).toHaveProperty('severity');
expect(mapping).toHaveProperty('category');
expect(mapping).toHaveProperty('userActionable');
expect(mapping).toHaveProperty('retryable');

const numericErrorCodes = Object.values(ErrorCode).filter(
(value): value is number => typeof value === 'number',
);
expect(numericErrorCodes).toContain(mapping.code);
expect(Object.values(Severity)).toContain(mapping.severity);
expect(Object.values(Category)).toContain(mapping.category);
expect(typeof mapping.message).toBe('string');
expect(typeof mapping.userActionable).toBe('boolean');
expect(typeof mapping.retryable).toBe('boolean');
});
});
});
});
74 changes: 74 additions & 0 deletions packages/hw-wallet-sdk/src/hardware-error-mappings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,77 @@ export const LEDGER_ERROR_MAPPINGS = {
userMessage: 'Ethereum app is out of date. Please update it to continue.',
},
};

export const BLE_ERROR_MAPPINGS = {
BLUETOOTH_PERMISSION_DENIED: {
code: ErrorCode.PermissionBluetoothDenied,
message: 'Bluetooth permission denied',
severity: Severity.Err,
category: Category.Configuration,
userMessage:
'Bluetooth permission is required to connect to your hardware wallet. Please enable it in your device settings.',
userActionable: true,
retryable: false,
Comment thread
mathieuartu marked this conversation as resolved.
Outdated
},
LOCATION_PERMISSION_DENIED: {
code: ErrorCode.PermissionLocationDenied,
message: 'Location permission denied',
severity: Severity.Err,
category: Category.Configuration,
userMessage:
'Location permission is required for Bluetooth scanning on Android. Please enable it in your device settings.',
userActionable: true,
retryable: false,
},
NEARBY_DEVICES_PERMISSION_DENIED: {
code: ErrorCode.PermissionNearbyDenied,
message: 'Nearby devices permission denied',
severity: Severity.Err,
category: Category.Configuration,
userMessage:
'Nearby devices permission is required to scan for your hardware wallet. Please enable it in your device settings.',
userActionable: true,
retryable: false,
},
BLUETOOTH_DISABLED: {
code: ErrorCode.BluetoothDisabled,
message: 'Bluetooth is turned off',
severity: Severity.Warning,
category: Category.Connection,
userMessage:
'Bluetooth is turned off. Please enable Bluetooth to connect to your hardware wallet.',
userActionable: true,
retryable: true,
},
BLUETOOTH_SCAN_FAILED: {
code: ErrorCode.BluetoothScanFailed,
message: 'Bluetooth scanning failed',
severity: Severity.Err,
category: Category.Connection,
userMessage: 'Unable to scan for Bluetooth devices. Please try again.',
userActionable: true,
retryable: true,
},
BLUETOOTH_CONNECTION_FAILED: {
code: ErrorCode.BluetoothConnectionFailed,
message: 'Bluetooth connection failed',
severity: Severity.Err,
category: Category.Connection,
userMessage:
'Failed to connect via Bluetooth. Please make sure your device is nearby and try again.',
userActionable: true,
retryable: true,
},
};

export const MOBILE_ERROR_MAPPINGS = {
NOT_SUPPORTED: {
code: ErrorCode.MobileNotSupported,
message: 'Operation not supported on mobile',
severity: Severity.Err,
category: Category.DeviceState,
userMessage: 'This operation is not supported on mobile devices.',
userActionable: false,
retryable: false,
},
};
Comment thread
mathieuartu marked this conversation as resolved.
10 changes: 10 additions & 0 deletions packages/hw-wallet-sdk/src/hardware-errors-enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ export enum ErrorCode {
DeviceStateEthAppClosed = 6003,
DeviceStateEthAppOutOfDate = 6004,

// Mobile/BLE Permission Errors
PermissionBluetoothDenied = 7000,
PermissionLocationDenied = 7001,
PermissionNearbyDenied = 7002,
BluetoothDisabled = 7100,
BluetoothScanFailed = 7101,
BluetoothConnectionFailed = 7102,
MobileNotSupported = 7300,

// Transaction
TxInsufficientFunds = 10000,

Expand All @@ -72,4 +81,5 @@ export enum Category {
UserAction = 'UserAction',
DeviceState = 'DeviceState',
Unknown = 'Unknown',
Configuration = 'Configuration',
}
Loading