|
| 1 | +/** |
| 2 | + * Platform and storage utilities that work across different environments (React Native, web, etc.) |
| 3 | + */ |
| 4 | + |
| 5 | +// User-defined overrides |
| 6 | +let platformOverride: { os: string; name: string } | null = null; |
| 7 | +let customStorageImplementation: StorageInterface | null = null; |
| 8 | + |
| 9 | +// Try to detect if we're in a React Native environment |
| 10 | +export const isReactNative = (): boolean => { |
| 11 | + try { |
| 12 | + return ( |
| 13 | + typeof navigator !== "undefined" && navigator.product === "ReactNative" |
| 14 | + ); |
| 15 | + } catch (e) { |
| 16 | + return false; |
| 17 | + } |
| 18 | +}; |
| 19 | + |
| 20 | +// Platform detection |
| 21 | +export const getPlatform = (): { os: string; name: string } => { |
| 22 | + // Return user-defined override if available |
| 23 | + if (platformOverride) { |
| 24 | + return platformOverride; |
| 25 | + } |
| 26 | + |
| 27 | + try { |
| 28 | + // Try to use React Native Platform if available |
| 29 | + if (isReactNative()) { |
| 30 | + try { |
| 31 | + // Dynamic import to avoid bundling issues |
| 32 | + const { Platform } = require("react-native"); |
| 33 | + const os = Platform.OS; |
| 34 | + const name = os.charAt(0).toUpperCase() + os.slice(1); |
| 35 | + return { os, name }; |
| 36 | + } catch (e) { |
| 37 | + console.warn("Failed to import Platform from react-native:", e); |
| 38 | + } |
| 39 | + } |
| 40 | + |
| 41 | + // Fallback to browser detection |
| 42 | + const userAgent = |
| 43 | + typeof navigator !== "undefined" ? navigator.userAgent : ""; |
| 44 | + if (/android/i.test(userAgent)) { |
| 45 | + return { os: "android", name: "Android" }; |
| 46 | + } |
| 47 | + if (/iPad|iPhone|iPod/.test(userAgent)) { |
| 48 | + return { os: "ios", name: "iOS" }; |
| 49 | + } |
| 50 | + if (/Windows/.test(userAgent)) { |
| 51 | + return { os: "windows", name: "Windows" }; |
| 52 | + } |
| 53 | + if (/Mac/.test(userAgent)) { |
| 54 | + return { os: "macos", name: "MacOS" }; |
| 55 | + } |
| 56 | + if (/Linux/.test(userAgent)) { |
| 57 | + return { os: "linux", name: "Linux" }; |
| 58 | + } |
| 59 | + |
| 60 | + // Default to web if we can't detect the platform |
| 61 | + return { os: "web", name: "Web" }; |
| 62 | + } catch (e) { |
| 63 | + console.warn("Error detecting platform:", e); |
| 64 | + return { os: "unknown", name: "Unknown" }; |
| 65 | + } |
| 66 | +}; |
| 67 | + |
| 68 | +/** |
| 69 | + * Override platform detection with a custom value |
| 70 | + * Useful for testing or when the automatic detection isn't working properly |
| 71 | + */ |
| 72 | +export const setPlatformOverride = ( |
| 73 | + platform: { os: string; name: string } | null |
| 74 | +): void => { |
| 75 | + platformOverride = platform; |
| 76 | +}; |
| 77 | + |
| 78 | +// Storage interface similar to AsyncStorage |
| 79 | +export interface StorageInterface { |
| 80 | + getItem: (key: string) => Promise<string | null>; |
| 81 | + setItem: (key: string, value: string) => Promise<void>; |
| 82 | + removeItem: (key: string) => Promise<void>; |
| 83 | +} |
| 84 | + |
| 85 | +/** |
| 86 | + * Set a custom storage implementation |
| 87 | + * Use this if you need to provide your own storage solution |
| 88 | + */ |
| 89 | +export const setCustomStorage = (storage: StorageInterface | null): void => { |
| 90 | + customStorageImplementation = storage; |
| 91 | +}; |
| 92 | + |
| 93 | +// Storage implementation |
| 94 | +export const getStorage = (): StorageInterface => { |
| 95 | + // Return user-defined storage if available |
| 96 | + if (customStorageImplementation) { |
| 97 | + return customStorageImplementation; |
| 98 | + } |
| 99 | + |
| 100 | + // Try to use React Native AsyncStorage if available |
| 101 | + if (isReactNative()) { |
| 102 | + try { |
| 103 | + // Dynamic import to avoid bundling issues |
| 104 | + const AsyncStorage = |
| 105 | + require("@react-native-async-storage/async-storage").default; |
| 106 | + return AsyncStorage; |
| 107 | + } catch (e) { |
| 108 | + console.warn("Failed to import AsyncStorage from react-native:", e); |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + // Fallback to browser localStorage with an async wrapper |
| 113 | + if (typeof localStorage !== "undefined") { |
| 114 | + return { |
| 115 | + getItem: async (key: string): Promise<string | null> => { |
| 116 | + return localStorage.getItem(key); |
| 117 | + }, |
| 118 | + setItem: async (key: string, value: string): Promise<void> => { |
| 119 | + localStorage.setItem(key, value); |
| 120 | + }, |
| 121 | + removeItem: async (key: string): Promise<void> => { |
| 122 | + localStorage.removeItem(key); |
| 123 | + }, |
| 124 | + }; |
| 125 | + } |
| 126 | + |
| 127 | + // Memory fallback if nothing else is available |
| 128 | + console.warn("No persistent storage available, using in-memory storage"); |
| 129 | + const memoryStorage: Record<string, string> = {}; |
| 130 | + return { |
| 131 | + getItem: async (key: string): Promise<string | null> => { |
| 132 | + return memoryStorage[key] || null; |
| 133 | + }, |
| 134 | + setItem: async (key: string, value: string): Promise<void> => { |
| 135 | + memoryStorage[key] = value; |
| 136 | + }, |
| 137 | + removeItem: async (key: string): Promise<void> => { |
| 138 | + delete memoryStorage[key]; |
| 139 | + }, |
| 140 | + }; |
| 141 | +}; |
| 142 | + |
| 143 | +/** |
| 144 | + * Get platform-specific URL for socket connection |
| 145 | + * On Android emulator, we need to replace localhost with 10.0.2.2 |
| 146 | + */ |
| 147 | +export const getPlatformSpecificURL = (baseUrl: string): string => { |
| 148 | + try { |
| 149 | + const platform = getPlatform(); |
| 150 | + const url = new URL(baseUrl); |
| 151 | + |
| 152 | + // For Android emulator, replace hostname with 10.0.2.2 |
| 153 | + if ( |
| 154 | + platform.os === "android" && |
| 155 | + (url.hostname === "localhost" || url.hostname === "127.0.0.1") |
| 156 | + ) { |
| 157 | + url.hostname = "10.0.2.2"; |
| 158 | + return url.toString(); |
| 159 | + } |
| 160 | + |
| 161 | + // For other platforms, use as provided |
| 162 | + return baseUrl; |
| 163 | + } catch (e) { |
| 164 | + console.warn("Error getting platform-specific URL:", e); |
| 165 | + return baseUrl; |
| 166 | + } |
| 167 | +}; |
0 commit comments