import { type IResourceAttributes } from '@multiplayer-app/session-recorder-common' import OptionalConstants from './constants.optional' import { Platform, Dimensions, PixelRatio } from 'react-native' import { version } from '../version' import { getAutoDetectedAppMetadata } from './app-metadata' // Global app metadata configuration for non-Expo apps let globalAppMetadata: { name?: string; version?: string; bundleId?: string } = {} // Cache for auto-detected metadata to avoid repeated file reads let autoDetectedMetadata: { name?: string version?: string bundleId?: string } | null = null export interface PlatformInfo { isExpo: boolean isReactNative: boolean platform: 'ios' | 'android' | 'web' | 'unknown' platformVersion?: string expoVersion?: string deviceType: string } export function detectPlatform(): PlatformInfo { try { // Check if we're in an Expo environment const isExpo = !!OptionalConstants?.expoVersion if (isExpo) { const expoVersion = OptionalConstants?.expoVersion const platform = OptionalConstants?.platform return { isExpo: true, isReactNative: true, platform: platform?.ios ? 'ios' : platform?.android ? 'android' : 'unknown', expoVersion, deviceType: 'expo', } } // Fallback to React Native detection return { isExpo: false, isReactNative: true, platform: Platform.OS as 'ios' | 'android' | 'web' | 'unknown', platformVersion: Platform.Version?.toString(), deviceType: Platform.OS, } } catch (error) { // Fallback for web or other environments return { isExpo: false, isReactNative: false, platform: 'unknown', deviceType: 'unknown', } } } export function getPlatformAttributes(): Record { const platformInfo = detectPlatform() const attributes: Record = { 'platform': platformInfo.isExpo ? 'expo' : 'react-native', 'device.type': platformInfo.deviceType, } if (platformInfo.platformVersion) { attributes['platform.version'] = platformInfo.platformVersion } if (platformInfo.expoVersion) { attributes['expo.version'] = platformInfo.expoVersion } return attributes } export function isExpoEnvironment(): boolean { return detectPlatform().isExpo } export function isReactNativeEnvironment(): boolean { return detectPlatform().isReactNative } /** * Configure app metadata for non-Expo React Native apps * Call this function in your app initialization to provide app information * * @example * ```typescript * import { configureAppMetadata } from '@multiplayer-app/session-recorder-react-native' * * // In your App.tsx or index.js * configureAppMetadata({ * name: 'My Awesome App', * version: '1.2.3', * bundleId: 'com.mycompany.myapp', * buildNumber: '123', * displayName: 'My App', * }) * ``` */ export function configureAppMetadata(metadata: { name?: string version?: string bundleId?: string buildNumber?: string displayName?: string }): void { globalAppMetadata = { ...globalAppMetadata, ...metadata } } /** * Get configured app metadata */ export function getConfiguredAppMetadata(): { name?: string version?: string bundleId?: string buildNumber?: string displayName?: string } { return { ...globalAppMetadata } } /** * Automatically detect app metadata from common configuration files * This runs without developer intervention */ function autoDetectAppMetadata(): { name?: string version?: string bundleId?: string } { if (autoDetectedMetadata) { return autoDetectedMetadata } try { // Get auto-detected metadata from build-time generated file const autoMetadata = getAutoDetectedAppMetadata() // Filter out undefined values const metadata: { name?: string; version?: string; bundleId?: string } = {} if (autoMetadata.name) metadata.name = autoMetadata.name if (autoMetadata.version) metadata.version = autoMetadata.version if (autoMetadata.bundleId) metadata.bundleId = autoMetadata.bundleId autoDetectedMetadata = metadata return metadata } catch (error) { // Silently fail - this is optional auto-detection autoDetectedMetadata = {} return {} } } /** * Enhanced app metadata detection with automatic fallbacks */ export function getAppMetadata(): { name?: string version?: string bundleId?: string } { // Priority order: // 1. Expo config (if available) // 2. Manually configured metadata // 3. Auto-detected metadata // 4. Fallbacks const expoMetadata = getExpoMetadata() const configuredMetadata = getConfiguredAppMetadata() const autoMetadata = autoDetectAppMetadata() return { name: expoMetadata.name || configuredMetadata.name || autoMetadata.name, version: expoMetadata.version || configuredMetadata.version || autoMetadata.version, bundleId: expoMetadata.bundleId || configuredMetadata.bundleId || autoMetadata.bundleId, } } /** * Get metadata from Expo config */ function getExpoMetadata(): { name?: string version?: string bundleId?: string } { const expoConfig = OptionalConstants?.expoConfig if (!expoConfig) return {} return { name: expoConfig.name, version: expoConfig.version, bundleId: expoConfig.ios?.bundleIdentifier || expoConfig.android?.package, } } export const getNavigatorInfo = (): IResourceAttributes => { const platformInfo = detectPlatform() const screenData = Dimensions.get('window') const screenDataScreen = Dimensions.get('screen') const pixelRatio = PixelRatio.get() // Get device type based on screen dimensions const getDeviceType = (): string => { const { width, height } = screenData const minDimension = Math.min(width, height) const maxDimension = Math.max(width, height) // Rough device type detection based on screen size if (maxDimension >= 1024) { return 'Tablet' } else if (minDimension >= 600) { return 'Large Phone' } else { return 'Phone' } } // Get orientation const getOrientation = (): string => { const { width, height } = screenData return width > height ? 'Landscape' : 'Portrait' } // Get OS version details const getOSInfo = (): string => { if (platformInfo.isExpo) { const platform = OptionalConstants?.platform if (platform?.ios) { return `iOS ${Platform.Version}` } else if (platform?.android) { return `Android ${Platform.Version}` } } if (Platform.OS === 'ios') { return `iOS ${Platform.Version}` } else if (Platform.OS === 'android') { return `Android ${Platform.Version}` } return `${Platform.OS} ${Platform.Version || 'Unknown'}` } // Get device info string const getDeviceInfo = (): string => { const deviceType = getDeviceType() const osInfo = getOSInfo() return `${deviceType} - ${osInfo}` } // Get browser/runtime info const getBrowserInfo = (): string => { if (platformInfo.isExpo) { return `Expo ${platformInfo.expoVersion || 'Unknown'} - React Native` } return 'React Native' } // Get screen size string const getScreenSize = (): string => { return `${Math.round(screenData.width)}x${Math.round(screenData.height)}` } const metadata = getAppMetadata() // Get app info with fallbacks for non-Expo apps const getAppInfo = (): string => { const appName = getAppName() const appVersion = getAppVersion() return `${appName} v${appVersion}` } // Get app name with automatic detection const getAppName = (): string => { if (metadata.name) return metadata.name // Try configured display name as fallback const configuredMetadata = getConfiguredAppMetadata() if (configuredMetadata.displayName) return configuredMetadata.displayName // Final fallback return 'React Native App' } // Get app version with automatic detection const getAppVersion = (): string => { if (metadata.version) return metadata.version // Final fallback return 'Unknown' } // Get bundle ID with automatic detection const getBundleId = (): string => { if (metadata.bundleId) return metadata.bundleId // Fallback return 'com.reactnative.app' } // Get build number with multiple fallback strategies const getBuildNumber = (): string => { // Try Expo config first const expoBuildNumber = OptionalConstants?.expoConfig?.ios?.buildNumber || OptionalConstants?.expoConfig?.android?.versionCode if (expoBuildNumber) return expoBuildNumber.toString() // Try configured metadata for non-Expo apps const configuredMetadata = getConfiguredAppMetadata() if (configuredMetadata.buildNumber) return configuredMetadata.buildNumber // Fallback return '1' } // Get hardware info const getHardwareInfo = (): string => { const pixelRatioInfo = `Pixel Ratio: ${pixelRatio}` const screenDensity = PixelRatio.getFontScale() return `${pixelRatioInfo}, Font Scale: ${screenDensity}` } return { // Core platform info platform: platformInfo.isExpo ? 'expo' : 'react-native', userAgent: getBrowserInfo(), timestamp: new Date().toISOString(), // Device and OS information deviceInfo: getDeviceInfo(), osInfo: getOSInfo(), browserInfo: getBrowserInfo(), // Screen information screenSize: getScreenSize(), pixelRatio: pixelRatio, orientation: getOrientation(), screenWidth: Math.round(screenData.width), screenHeight: Math.round(screenData.height), screenScale: pixelRatio, // Device capabilities hardwareConcurrency: 1, // React Native doesn't expose CPU cores directly cookiesEnabled: 'N/A', // Not applicable in React Native // App information packageVersion: version, appInfo: getAppInfo(), appName: getAppName(), appVersion: getAppVersion(), bundleId: getBundleId(), buildNumber: getBuildNumber(), // Platform specific platformType: platformInfo.platform, platformVersion: platformInfo.platformVersion, expoVersion: platformInfo.expoVersion, deviceType: getDeviceType(), // Performance and hardware hardwareInfo: getHardwareInfo(), // Screen details screenDensity: PixelRatio.getFontScale(), screenScaleFactor: pixelRatio, windowWidth: Math.round(screenData.width), windowHeight: Math.round(screenData.height), fullScreenWidth: Math.round(screenDataScreen.width), fullScreenHeight: Math.round(screenDataScreen.height), // Environment info isExpo: platformInfo.isExpo, isReactNative: platformInfo.isReactNative, environment: platformInfo.isExpo ? 'expo' : 'react-native', // System type identifier (previously in system tags) systemType: 'mobile', // Additional platform attributes // ...getPlatformAttributes(), } }