@silicon.js/device-id
v1.0.9
Published
A robust React Native package for generating, storing, and managing unique device identifiers. This package provides persistent device IDs across app sessions with fallback support and a context-based API.
Maintainers
Readme
React Native Device ID Package
A robust React Native package for generating, storing, and managing unique device identifiers. This package provides persistent device IDs across app sessions with fallback support and a context-based API.
Features
- 🔑 Platform-native IDs: Uses iOS Vendor ID and Android ID when available
- 💾 Persistent Storage: Stores device ID in AsyncStorage for consistency
- 🔄 Automatic Fallback: Generates UUID v4 if native IDs are unavailable
- 🎯 Context-based API: Easy access throughout your component tree
- 🎣 Flexible Hooks: Use with or without context
- ⚡ Async Support: Built with async/await for optimal performance
- 🛡️ Error Handling: Comprehensive error catching and callbacks
- 🔧 Device Management: Delete and regenerate IDs as needed
Installation
npm install expo-application @react-native-async-storage/async-storage uuidor
yarn add expo-application @react-native-async-storage/async-storage uuidInstall Types (TypeScript)
npm install --save-dev @types/uuidSetup
1. Configure Storage Key
Create a constants file or add to your existing constants:
// constants.ts or constants/index.ts
export const DEVICE_ID_STORAGE_KEY = '@app:deviceId';2. Wrap Your App with DeviceIdProvider
import { DeviceIdProvider } from './path-to-package';
function App() {
const handleSuccess = (deviceId: string) => {
console.log('Device ID loaded:', deviceId);
// Send to analytics, backend, etc.
};
const handleError = (error: Error) => {
console.error('Device ID error:', error);
};
return (
<DeviceIdProvider onSuccess={handleSuccess} onError={handleError} enabled={true}>
<YourApp />
</DeviceIdProvider>
);
}Usage
Using the Context Hook (Recommended)
Access device ID anywhere in your component tree:
import { useDeviceIdContext } from './path-to-package';
function MyComponent() {
const { deviceId, isLoading, error, getDeviceId, deleteDeviceId, regenerateDeviceId } =
useDeviceIdContext();
if (isLoading) {
return <Text>Loading device ID...</Text>;
}
if (error) {
return <Text>Error: {error.message}</Text>;
}
return (
<View>
<Text>Device ID: {deviceId}</Text>
<Button title="Refresh ID" onPress={getDeviceId} />
<Button title="Regenerate ID" onPress={regenerateDeviceId} />
<Button title="Delete ID" onPress={deleteDeviceId} />
</View>
);
}Using the Hook Directly
Use without context for more control:
import { useDeviceId } from './path-to-package';
function StandaloneComponent() {
const { deviceId, isLoading, error, regenerateDeviceId } = useDeviceId({
enabled: true,
onSuccess: (id) => {
console.log('Got device ID:', id);
},
onError: (err) => {
console.error('Failed to get device ID:', err);
},
});
return (
<View>
{isLoading ? (
<ActivityIndicator />
) : (
<>
<Text>ID: {deviceId}</Text>
<Button title="New ID" onPress={regenerateDeviceId} />
</>
)}
</View>
);
}Manual Device ID Generation
Use the utility function directly:
import { deviceIdUtils } from './path-to-package';
async function generateNewId() {
const deviceId = await deviceIdUtils.generate();
console.log('New device ID:', deviceId);
return deviceId;
}API Reference
DeviceIdProvider
Provider component that manages device ID state and makes it available via context.
Props:
onSuccess?: (deviceId: string) => void | Promise<void>- Optional callback when device ID is successfully loaded/generatedonError?: (error: Error) => void- Optional callback when an error occursenabled?: boolean- Whether to automatically load device ID on mount (default:true)methods?: UseDeviceIdResult- Optional custom methods to override default behaviorchildren: React.ReactNode- Child components
useDeviceIdContext()
Hook to access device ID context. Must be used within a DeviceIdProvider.
Returns:
{
deviceId: string | null;
isLoading: boolean;
error: Error | null;
getDeviceId: () => Promise<string | null>;
deleteDeviceId: () => Promise<void>;
regenerateDeviceId: () => Promise<void>;
}Throws: Error if used outside of DeviceIdProvider
useDeviceId(options?)
Hook for managing device ID independently (works without provider).
Parameters:
{
onSuccess?: (deviceId: string) => void | Promise<void>;
onError?: (error: Error) => void;
enabled?: boolean; // default: true
}Returns:
{
deviceId: string | null;
isLoading: boolean;
error: Error | null;
getDeviceId: () => Promise<string | null>;
deleteDeviceId: () => Promise<void>;
regenerateDeviceId: () => Promise<void>;
}deviceIdUtils.generate()
Utility function to generate a new device ID.
Returns: Promise<string>
Behavior:
- iOS: Returns
Application.getIosIdForVendorAsync()result - Android: Returns
Application.getAndroidId()result - Fallback: Returns UUID v4 if native methods fail
Methods
getDeviceId()
Retrieves the current device ID from storage.
const id = await getDeviceId();
console.log('Current device ID:', id);deleteDeviceId()
Removes the device ID from storage and clears the state.
await deleteDeviceId();
console.log('Device ID deleted');regenerateDeviceId()
Generates a new device ID and replaces the existing one.
await regenerateDeviceId();
console.log('Device ID regenerated');Device ID Behavior
Generation Strategy
iOS: Uses
Application.getIosIdForVendorAsync()- Unique per vendor (developer)
- Persists across app reinstalls if another app from same vendor is installed
- Changes if all apps from vendor are uninstalled
Android: Uses
Application.getAndroidId()- Unique per app installation
- Changes on factory reset or app reinstall
Fallback: Generates UUID v4
- Used if native methods fail
- Cryptographically random
Storage
- Device IDs are stored in AsyncStorage using the key defined in
DEVICE_ID_STORAGE_KEY - IDs persist across app sessions
- IDs are automatically loaded on app startup (if
enabled: true)
Lifecycle
- On first app launch: Generate and store new ID
- On subsequent launches: Load existing ID from storage
- If storage fails: Generate new ID but continue with in-memory value
- Manual operations: Delete or regenerate as needed
Complete Example
import React, { useEffect } from 'react';
import { View, Text, Button, ActivityIndicator, StyleSheet } from 'react-native';
import { DeviceIdProvider, useDeviceIdContext } from './path-to-package';
function DeviceInfo() {
const { deviceId, isLoading, error, getDeviceId, deleteDeviceId, regenerateDeviceId } =
useDeviceIdContext();
useEffect(() => {
if (deviceId) {
// Send to analytics or backend
console.log('Device registered:', deviceId);
}
}, [deviceId]);
if (isLoading) {
return (
<View style={styles.container}>
<ActivityIndicator size="large" />
<Text>Loading device ID...</Text>
</View>
);
}
if (error) {
return (
<View style={styles.container}>
<Text style={styles.error}>Error: {error.message}</Text>
<Button title="Retry" onPress={getDeviceId} />
</View>
);
}
return (
<View style={styles.container}>
<Text style={styles.title}>Device Information</Text>
<View style={styles.idContainer}>
<Text style={styles.label}>Device ID:</Text>
<Text style={styles.id}>{deviceId}</Text>
</View>
<View style={styles.actions}>
<Button title="Refresh" onPress={getDeviceId} />
<Button title="Regenerate" onPress={regenerateDeviceId} color="#ff9500" />
<Button title="Delete" onPress={deleteDeviceId} color="#ff3b30" />
</View>
</View>
);
}
export default function App() {
const handleSuccess = async (deviceId: string) => {
console.log('Device ID ready:', deviceId);
// Example: Send to backend
// await api.registerDevice(deviceId);
};
const handleError = (error: Error) => {
console.error('Device ID error:', error);
// Example: Log to error tracking service
// Sentry.captureException(error);
};
return (
<DeviceIdProvider onSuccess={handleSuccess} onError={handleError} enabled={true}>
<DeviceInfo />
</DeviceIdProvider>
);
}Use Cases
- User Analytics: Track unique users without authentication
- Device Fingerprinting: Identify devices for security purposes
- Session Management: Maintain sessions across app restarts
- A/B Testing: Consistently assign users to test groups
- Rate Limiting: Limit API requests per device
- Fraud Detection: Identify suspicious device patterns
Error Handling
The package handles errors gracefully:
const { deviceId, error } = useDeviceIdContext();
if (error) {
// Handle different error scenarios
if (error.message.includes('storage')) {
// Storage access issue
console.log('Storage error, using temporary ID');
} else if (error.message.includes('generate')) {
// Generation failure
console.log('Failed to generate ID');
}
}Platform Considerations
iOS
- Vendor ID may change if user uninstalls all apps from the same vendor
- Requires
expo-applicationpermissions
Android
- Android ID changes on factory reset
- Different behavior on Android 8.0+ (scoped per app signing key)
Web (Not Supported)
- This package is designed for iOS and Android only
- Web fallback would use UUID v4
Best Practices
- Initialize Early: Wrap your root component with
DeviceIdProvider - Handle Errors: Always provide
onErrorcallback for production apps - Privacy: Inform users about device tracking in your privacy policy
- Don't Rely Solely: Use alongside other authentication methods
- Secure Storage: Consider encrypting sensitive device data
- GDPR Compliance: Provide way for users to delete their device ID
Dependencies
expo-application: For native device IDs@react-native-async-storage/async-storage: For persistent storageuuid: For fallback UUID generationreact: Peer dependencyreact-native: Peer dependency
Troubleshooting
Device ID keeps changing
- Check if AsyncStorage is working correctly
- Verify
DEVICE_ID_STORAGE_KEYis consistent - Ensure app isn't being uninstalled between tests
"Must be used within DeviceIdProvider" error
- Make sure
useDeviceIdContext()is called inside a component wrapped byDeviceIdProvider - Check that provider is in a parent component
Storage errors
- Verify
@react-native-async-storage/async-storageis properly installed - Check storage permissions on device
- Clear app data and retry
