import { Platform } from 'react-native' import { SessionType, type ISession, } from '@multiplayer-app/session-recorder-common' import { logger } from '../utils' import type { SessionState } from '../types' // Safe import for AsyncStorage with web fallback let AsyncStorage: any = null const isWeb = Platform.OS === 'web' if (!isWeb) { try { AsyncStorage = require('@react-native-async-storage/async-storage').default } catch (error) { console.warn('AsyncStorage not available:', error) } } else { // Web fallback using localStorage AsyncStorage = { getItem: (_key: string) => Promise.resolve(null), // Simplified for web setItem: (_key: string, _value: string) => Promise.resolve(undefined), removeItem: (_key: string) => Promise.resolve(undefined), multiRemove: (_keys: string[]) => Promise.resolve(undefined), } } interface CacheData { sessionId: string | null sessionType: SessionType | null sessionState: SessionState | null sessionObject: ISession | null floatingButtonPosition: { x: number; y: number } | null } export class StorageService { private static readonly SESSION_ID_KEY = 'session_id' private static readonly SESSION_TYPE_KEY = 'session_type' private static readonly SESSION_STATE_KEY = 'session_state' private static readonly SESSION_OBJECT_KEY = 'session_object' private static readonly FLOATING_BUTTON_POSITION_KEY = 'floating_button_position' private static cache: CacheData = { sessionId: null, sessionType: null, sessionState: null, sessionObject: null, floatingButtonPosition: null, } private static cacheInitialized = false private static instance: StorageService | null = null private static positionSaveTimeout: any | null = null private constructor() { // Private constructor for singleton } static getInstance(): StorageService { if (!StorageService.instance) { StorageService.instance = new StorageService() StorageService.initialize() } return StorageService.instance } private static async initializeCache(): Promise { if (StorageService.cacheInitialized) return try { const [ sessionId, sessionType, sessionState, sessionObject, floatingButtonPosition, ] = await Promise.all([ AsyncStorage.getItem(StorageService.SESSION_ID_KEY), AsyncStorage.getItem(StorageService.SESSION_TYPE_KEY), AsyncStorage.getItem(StorageService.SESSION_STATE_KEY), AsyncStorage.getItem(StorageService.SESSION_OBJECT_KEY), AsyncStorage.getItem(StorageService.FLOATING_BUTTON_POSITION_KEY), ]) StorageService.cache = { sessionId, sessionType: sessionType as SessionType | null, sessionState: sessionState as SessionState | null, sessionObject: sessionObject ? JSON.parse(sessionObject) : null, floatingButtonPosition: floatingButtonPosition ? JSON.parse(floatingButtonPosition) : null, } StorageService.cacheInitialized = true } catch (error) { // Failed to initialize cache - silently continue StorageService.cacheInitialized = true // Mark as initialized to prevent retries } } saveSessionId(sessionId: string): void { try { StorageService.cache.sessionId = sessionId AsyncStorage.setItem(StorageService.SESSION_ID_KEY, sessionId).catch( (_error: any) => { // Failed to persist session ID - silently continue }, ) } catch (error) { // Failed to save session ID - silently continue throw error } } getSessionId(): string | null { return StorageService.cache.sessionId } saveSessionType(sessionType: SessionType): void { try { StorageService.cache.sessionType = sessionType AsyncStorage.setItem(StorageService.SESSION_TYPE_KEY, sessionType).catch( (_error: any) => { // Failed to persist session type - silently continue }, ) } catch (error) { // Failed to save session type - silently continue throw error } } getSessionType(): SessionType | null { return StorageService.cache.sessionType } saveSessionState(state: SessionState): void { try { StorageService.cache.sessionState = state AsyncStorage.setItem(StorageService.SESSION_STATE_KEY, state).catch( (_error: any) => { // Failed to persist session state - silently continue }, ) } catch (error) { // Failed to save session state - silently continue throw error } } getSessionState(): SessionState | null { return StorageService.cache.sessionState } saveSessionObject(session: ISession): void { try { StorageService.cache.sessionObject = session AsyncStorage.setItem( StorageService.SESSION_OBJECT_KEY, JSON.stringify(session), ).catch((_error: any) => { // Failed to persist session object - silently continue }) } catch (error) { // Failed to save session object - silently continue throw error } } getSessionObject(): ISession | null { return StorageService.cache.sessionObject } clearSessionData(): void { try { // Clear cache immediately StorageService.cache = { ...StorageService.cache, sessionId: null, sessionType: null, sessionState: null, sessionObject: null, } // Clear persistent storage asynchronously AsyncStorage.multiRemove([ StorageService.SESSION_ID_KEY, StorageService.SESSION_TYPE_KEY, StorageService.SESSION_STATE_KEY, StorageService.SESSION_OBJECT_KEY, ]).catch((_error: any) => { // Failed to clear session data from storage - silently continue }) } catch (error) { // Failed to clear session data - silently continue throw error } } getAllSessionData(): Omit { return { sessionId: StorageService.cache.sessionId, sessionType: StorageService.cache.sessionType, sessionState: StorageService.cache.sessionState, sessionObject: StorageService.cache.sessionObject, } } saveFloatingButtonPosition(position: { x: number; y: number }): void { try { StorageService.cache.floatingButtonPosition = position // Debounce AsyncStorage writes to avoid excessive I/O if (StorageService.positionSaveTimeout) { clearTimeout(StorageService.positionSaveTimeout) } StorageService.positionSaveTimeout = setTimeout(() => { AsyncStorage.setItem( StorageService.FLOATING_BUTTON_POSITION_KEY, JSON.stringify(position), ).catch((error: any) => { logger.error( 'StorageService', 'Failed to persist floating button position', error, ) }) }, 100) // 100ms debounce } catch (error) { logger.error( 'StorageService', 'Failed to save floating button position', error, ) throw error } } getFloatingButtonPosition(): { x: number; y: number } | null { return StorageService.cache.floatingButtonPosition } // Initialize cache on first use - call this method when the service is first used static async initialize(): Promise { await StorageService.initializeCache() } }