Promise Rejection Patterns
All asynchronous operations in the React Native SSH SFTP library return Promises that reject when errors occur. This provides a consistent error handling pattern across the API.
Basic Error Handling
Use try/catch blocks with async/await:
try {
const client = await SSHClient.connectWithPassword(
'10.0.0.10',
22,
'user',
'password'
);
const output = await client.execute('ls -la');
console.log(output);
} catch (error) {
console.error('Operation failed:', error);
}
Or use .catch() with Promises:
SSHClient.connectWithPassword('10.0.0.10', 22, 'user', 'password')
.then(client => client.execute('ls -la'))
.then(output => console.log(output))
.catch(error => console.error('Operation failed:', error));
Error Types
The library uses a generic error type from the native modules:
type CBError = any; // eslint-disable-line @typescript-eslint/no-explicit-any
Errors can originate from:
- Connection errors: Network issues, authentication failures, timeouts
- SFTP errors: File not found, permission denied, disk full
- SSH command errors: Command execution failures, shell errors
- Native module errors: Platform-specific errors from iOS/Android implementations
Common Errors
Authentication Errors
Occur when credentials are invalid or key-based authentication fails:
try {
const client = await SSHClient.connectWithPassword(
host,
port,
'wronguser',
'wrongpass'
);
} catch (error) {
// Common authentication errors:
// - Invalid credentials
// - Account locked
// - SSH service not available
console.error('Authentication failed:', error);
}
Connection Errors
Occur when the client cannot establish a connection:
try {
const client = await SSHClient.connectWithKey(
'unreachable-host.example.com',
22,
'user',
privateKey
);
} catch (error) {
// Common connection errors:
// - Host unreachable
// - Connection timeout
// - Connection refused
// - Network unreachable
console.error('Connection failed:', error);
}
SFTP Errors
Occur during file transfer operations:
try {
await client.sftpDownload('/nonexistent/file.txt', '/local/path/');
} catch (error) {
// Common SFTP errors:
// - File not found
// - Permission denied
// - Disk full
// - Directory doesn't exist
console.error('SFTP operation failed:', error);
}
Command Execution Errors
Occur when SSH commands fail:
try {
const output = await client.execute('invalid-command');
} catch (error) {
// Common command errors:
// - Command not found
// - Permission denied
// - Command returned non-zero exit code
console.error('Command execution failed:', error);
}
Error Handling Best Practices
1. Always Handle Errors
Never ignore Promise rejections. Always implement error handling for all asynchronous operations.
// Bad: No error handling
const client = await SSHClient.connectWithPassword(host, port, user, pass);
const output = await client.execute('rm -rf /');
// Good: Comprehensive error handling
try {
const client = await SSHClient.connectWithPassword(host, port, user, pass);
const output = await client.execute('ls');
console.log(output);
} catch (error) {
console.error('Operation failed:', error);
// Implement recovery logic
}
2. Clean Up Resources on Error
Always disconnect clients, even when errors occur:
let client;
try {
client = await SSHClient.connectWithPassword(host, port, username, password);
await client.execute('some-command');
await client.sftpUpload(localFile, remoteFile);
} catch (error) {
console.error('Error during operations:', error);
// Handle error
} finally {
// Always cleanup
if (client) {
client.disconnect();
}
}
3. Separate Connection and Operation Errors
Handle connection errors separately from operation errors:
let client;
try {
// Connection phase
client = await SSHClient.connectWithPassword(host, port, username, password);
} catch (error) {
console.error('Failed to connect:', error);
// Show connection error to user
return;
}
try {
// Operation phase
const files = await client.sftpLs('/home/user');
await client.sftpDownload('/home/user/file.txt', '/local/path/');
} catch (error) {
console.error('Operation failed:', error);
// Show operation error to user
} finally {
client.disconnect();
}
4. Provide User-Friendly Error Messages
Translate technical errors into user-friendly messages:
function getErrorMessage(error: any): string {
const errorStr = String(error).toLowerCase();
if (errorStr.includes('authentication')) {
return 'Invalid username or password';
}
if (errorStr.includes('timeout') || errorStr.includes('unreachable')) {
return 'Cannot reach server. Check your connection.';
}
if (errorStr.includes('permission')) {
return 'Permission denied. Check file permissions.';
}
if (errorStr.includes('not found')) {
return 'File or directory not found';
}
return 'An unexpected error occurred. Please try again.';
}
try {
const client = await SSHClient.connectWithPassword(host, port, user, pass);
await client.execute('ls');
} catch (error) {
const message = getErrorMessage(error);
Alert.alert('Error', message);
}
5. Implement Retry Logic
For transient errors, implement retry with exponential backoff:
async function connectWithRetry(
host: string,
port: number,
username: string,
password: string,
maxRetries = 3
): Promise<SSHClient> {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
return await SSHClient.connectWithPassword(host, port, username, password);
} catch (error) {
lastError = error;
console.log(`Connection attempt ${i + 1} failed, retrying...`);
// Exponential backoff: 1s, 2s, 4s
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
}
}
throw lastError;
}
// Usage
try {
const client = await connectWithRetry(host, port, username, password);
} catch (error) {
console.error('Failed after retries:', error);
}
6. Handle File Transfer Cancellation
Properly handle cancelled uploads and downloads:
const client = await SSHClient.connectWithPassword(host, port, username, password);
let uploadInProgress = false;
const startUpload = async () => {
uploadInProgress = true;
try {
await client.sftpUpload(localFile, remoteFile);
console.log('Upload completed');
} catch (error) {
if (uploadInProgress) {
// Upload failed
console.error('Upload failed:', error);
} else {
// Upload was cancelled
console.log('Upload cancelled');
}
} finally {
uploadInProgress = false;
}
};
const cancelUpload = () => {
uploadInProgress = false;
client.sftpCancelUpload();
};
7. Log Errors for Debugging
Implement comprehensive error logging:
function logError(context: string, error: any) {
console.error(`[${context}] Error:`, {
message: error.message || String(error),
stack: error.stack,
timestamp: new Date().toISOString(),
});
}
try {
const client = await SSHClient.connectWithPassword(host, port, username, password);
await client.execute('ls');
} catch (error) {
logError('SSH Connection', error);
throw error;
}
Using Callbacks (Alternative Pattern)
The library also supports callback-based error handling:
SSHClient.connectWithPassword(
host,
port,
username,
password,
(error) => {
if (error) {
console.error('Connection failed:', error);
return;
}
console.log('Connected successfully');
}
).then(client => {
// Use client
}).catch(error => {
// Also handle Promise rejection
});
While callbacks are supported, using async/await with try/catch is the recommended approach for cleaner, more maintainable code.
React Native Specific Considerations
Network State
Check network connectivity before attempting connections:
import NetInfo from '@react-native-community/netinfo';
const state = await NetInfo.fetch();
if (!state.isConnected) {
throw new Error('No network connection');
}
const client = await SSHClient.connectWithPassword(host, port, username, password);
Error Boundaries
Use React Error Boundaries to catch errors in components:
class SSHErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('SSH Error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <ErrorScreen error={this.state.error} />;
}
return this.props.children;
}
}
iOS Simulator Limitation
The library does not currently support the iOS simulator. Always test on physical iOS devices.
import { Platform } from 'react-native';
if (Platform.OS === 'ios' && __DEV__) {
// Warn users in development
console.warn('iOS simulator is not supported. Use a physical device.');
}
Next Steps