import { Platform } from 'react-native'; import { Logger } from './logger'; interface DeviceAppInfo { app_version: string; build_number: string; package_name: string; device_model_name: string; device_manufacturer: string; device_id: string; os_name: string; os_version: string; is_tablet: boolean; is_emulator: boolean; } /** * Helper class to automatically detect device and app information * Supports both Expo and bare React Native: * 1. Tries react-native-device-info first (works in bare RN and Expo custom builds) * 2. Falls back to Expo modules (for Expo Go where native modules don't work) * 3. Returns safe defaults if neither is available */ export class DeviceInfoHelper { private static expoApplication: any = null; private static expoDevice: any = null; private static expoConstants: any = null; private static deviceInfo: any = null; private static initialized = false; /** * Try to load react-native-device-info first, then Expo modules * Uses console.log during init to avoid Logger dependency issues if module loading fails */ private static async initialize(): Promise { if (this.initialized) return; // FIRST: Try react-native-device-info (preferred - works in bare RN and Expo custom builds) // This is a direct dependency of the SDK, so it should always be available except in Expo Go try { // eslint-disable-next-line @typescript-eslint/no-var-requires const rnDeviceInfo = require('react-native-device-info'); // Validate the module is actually loaded and functional if ( rnDeviceInfo && typeof (rnDeviceInfo.default || rnDeviceInfo).getVersion === 'function' ) { this.deviceInfo = rnDeviceInfo.default || rnDeviceInfo; this.initialized = true; // Use console.log to avoid any Logger issues during early init console.log( '[MagicPixel DeviceInfoHelper] Using react-native-device-info' ); return; } } catch (error) { // react-native-device-info not available (likely Expo Go) console.log( '[MagicPixel DeviceInfoHelper] react-native-device-info not available, trying Expo modules' ); } // SECOND: Fall back to Expo modules (for Expo Go where native modules don't work) try { // eslint-disable-next-line @typescript-eslint/no-var-requires const expoApp = require('expo-application'); // eslint-disable-next-line @typescript-eslint/no-var-requires const expoDevice = require('expo-device'); // eslint-disable-next-line @typescript-eslint/no-var-requires const expoConst = require('expo-constants'); // Validate modules are actually loaded (not undefined placeholders from Metro) if ( expoApp && expoDevice && expoConst && typeof expoApp.nativeApplicationVersion !== 'undefined' ) { this.expoApplication = expoApp; this.expoDevice = expoDevice; this.expoConstants = expoConst.default || expoConst; console.log('[MagicPixel DeviceInfoHelper] Using Expo modules'); } } catch (error) { // Expo modules not available console.log( '[MagicPixel DeviceInfoHelper] Expo modules not available, using safe defaults' ); } this.initialized = true; } /** * Get comprehensive app and device information * All methods are async-safe and handle errors gracefully */ static async getAppInfo(): Promise { await this.initialize(); try { // Use react-native-device-info if available (preferred) if (this.deviceInfo) { return await this.getAppInfoFromDeviceInfo(); } // Fall back to Expo modules (for Expo Go) if (this.expoApplication && this.expoDevice) { return await this.getAppInfoFromExpo(); } // Return safe fallback values return this.getSafeDefaults(); } catch (error) { // Use console.log here as well to be safe console.error('[MagicPixel DeviceInfoHelper] Error getting device info:', error); return this.getSafeDefaults(); } } /** * Get device info using Expo modules (works in Expo Go) */ private static async getAppInfoFromExpo(): Promise { const version = this.expoApplication.nativeApplicationVersion || 'unknown'; const buildNumber = this.expoApplication.nativeBuildVersion || 'unknown'; const packageName = this.expoApplication.applicationId || 'unknown'; return { // App information app_version: version, build_number: buildNumber, package_name: packageName, // Device information device_model_name: this.expoDevice.modelName || 'unknown', device_manufacturer: this.expoDevice.manufacturer || 'unknown', device_id: this.expoConstants.sessionId || 'unknown', // OS information os_name: Platform.OS, os_version: this.expoDevice.osVersion || Platform.Version?.toString() || 'unknown', // Additional useful info // DeviceType enum: UNKNOWN=0, PHONE=1, TABLET=2, DESKTOP=3, TV=4 is_tablet: this.expoDevice.deviceType === 2, // DeviceType.TABLET = 2 is_emulator: !this.expoDevice.isDevice, }; } /** * Get device info using react-native-device-info (for bare RN) */ private static async getAppInfoFromDeviceInfo(): Promise { const [version, buildNumber, uniqueId, bundleId] = await Promise.all([ this.deviceInfo.getVersion(), this.deviceInfo.getBuildNumber(), this.deviceInfo.getUniqueId(), this.deviceInfo.getBundleId(), ]); return { // App information app_version: version, build_number: buildNumber, package_name: bundleId, // Device information device_model_name: this.deviceInfo.getModel(), device_manufacturer: this.deviceInfo.getManufacturerSync(), device_id: uniqueId, // OS information os_name: Platform.OS, os_version: this.deviceInfo.getSystemVersion(), // Additional useful info is_tablet: this.deviceInfo.isTablet(), is_emulator: await this.deviceInfo.isEmulator(), }; } /** * Safe default values when no device info library is available */ private static getSafeDefaults(): DeviceAppInfo { return { app_version: 'unknown', build_number: 'unknown', package_name: 'unknown', device_model_name: 'unknown', device_manufacturer: 'unknown', device_id: 'unknown', os_name: Platform.OS, os_version: Platform.Version?.toString() || 'unknown', is_tablet: false, is_emulator: false, }; } /** * Get app version in format: "1.0.0 (123)" * Combines version and build number for better tracking */ static async getFormattedAppVersion(): Promise { try { const info = await this.getAppInfo(); return `${info.app_version} (${info.build_number})`; } catch (error) { Logger.logError('Error getting formatted app version:', error); return 'unknown'; } } }