UNPKG

@oxyhq/services

Version:

OxyHQ Expo/React Native SDK — UI components, screens, and native features

222 lines (208 loc) 6.84 kB
"use strict"; import { useEffect, useRef, useState } from 'react'; import { AppState, Platform } from 'react-native'; import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { OxyContextProvider } from "../context/OxyContext.js"; import { QueryClientProvider, focusManager, onlineManager } from '@tanstack/react-query'; import { setupFonts } from "./FontLoader.js"; import { Toaster } from '../../lib/sonner'; import { createQueryClient } from "../hooks/queryClient.js"; import { createPlatformStorage } from "../utils/storageHelpers.js"; // Initialize fonts automatically import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; setupFonts(); // Detect if running on web const isWeb = Platform.OS === 'web'; // Conditionally import components let KeyboardProvider = ({ children }) => children; let BottomSheetRouter = null; let SignInModal = null; // KeyboardProvider only on native if (!isWeb) { try { KeyboardProvider = require('react-native-keyboard-controller').KeyboardProvider; } catch { // KeyboardProvider not available } } // BottomSheetRouter works on all platforms try { BottomSheetRouter = require('./BottomSheetRouter').default; } catch { // BottomSheetRouter not available } // SignInModal works on all platforms try { SignInModal = require('./SignInModal').default; } catch { // SignInModal not available } /** * OxyProvider - Universal provider for Expo apps (native + web) * * Zero-config authentication and session management: * - Native (iOS/Android): Keychain-based identity, bottom sheet auth UI * - Web: FedCM cross-domain SSO, popup fallback * * Usage: * ```tsx * import { OxyProvider, useAuth } from '@oxyhq/services'; * * function App() { * return ( * <OxyProvider baseURL="https://api.oxy.so"> * <YourApp /> * </OxyProvider> * ); * } * * function MyComponent() { * const { isAuthenticated, user, signIn, signOut } = useAuth(); * * if (!isAuthenticated) { * return <OxySignInButton />; * } * return <Text>Welcome, {user?.username}!</Text>; * } * ``` */ const OxyProvider = ({ oxyServices, children, onAuthStateChange, storageKeyPrefix, baseURL, authWebUrl, authRedirectUri, queryClient: providedQueryClient }) => { // Simple storage initialization for query persistence const storageRef = useRef(null); const queryClientRef = useRef(null); const [queryClient, setQueryClient] = useState(null); useEffect(() => { if (providedQueryClient) { queryClientRef.current = providedQueryClient; setQueryClient(providedQueryClient); return; } // Initialize storage and create query client let mounted = true; createPlatformStorage().then(storage => { if (mounted && !queryClientRef.current) { storageRef.current = storage; const client = createQueryClient(storage); queryClientRef.current = client; setQueryClient(client); } }).catch(error => { // If storage fails, create query client without persistence if (mounted && !queryClientRef.current) { if (__DEV__) { console.warn('[OxyProvider] Failed to initialize storage for query persistence', error); } const client = createQueryClient(null); queryClientRef.current = client; setQueryClient(client); } }); return () => { mounted = false; }; }, [providedQueryClient]); // Hook React Query focus manager into app state (native) or visibility (web) useEffect(() => { if (isWeb) { // Web: use document visibility const handleVisibilityChange = () => { focusManager.setFocused(document.visibilityState === 'visible'); }; document.addEventListener('visibilitychange', handleVisibilityChange); return () => { document.removeEventListener('visibilitychange', handleVisibilityChange); }; } else { // Native: use AppState const subscription = AppState.addEventListener('change', state => { focusManager.setFocused(state === 'active'); }); return () => { subscription.remove(); }; } }, []); // Setup network status monitoring for offline detection useEffect(() => { let cleanup; const setupNetworkMonitoring = async () => { try { if (isWeb) { // Web: use navigator.onLine onlineManager.setOnline(navigator.onLine); const handleOnline = () => onlineManager.setOnline(true); const handleOffline = () => onlineManager.setOnline(false); window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); cleanup = () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); }; } else { // Native: try to use NetInfo try { const NetInfo = await import('@react-native-community/netinfo'); const state = await NetInfo.default.fetch(); onlineManager.setOnline(state.isConnected ?? true); const unsubscribe = NetInfo.default.addEventListener(state => { onlineManager.setOnline(state.isConnected ?? true); }); cleanup = () => unsubscribe(); } catch { // NetInfo not available, default to online onlineManager.setOnline(true); } } } catch (error) { // Default to online if detection fails onlineManager.setOnline(true); } }; setupNetworkMonitoring(); return () => { cleanup?.(); }; }, []); // Ensure we have a valid QueryClient if (!queryClient) { return null; } // Core content that works on all platforms const coreContent = /*#__PURE__*/_jsx(QueryClientProvider, { client: queryClient, children: /*#__PURE__*/_jsxs(OxyContextProvider, { oxyServices: oxyServices, baseURL: baseURL, authWebUrl: authWebUrl, authRedirectUri: authRedirectUri, storageKeyPrefix: storageKeyPrefix, onAuthStateChange: onAuthStateChange, children: [children, BottomSheetRouter && /*#__PURE__*/_jsx(BottomSheetRouter, {}), SignInModal && /*#__PURE__*/_jsx(SignInModal, {}), /*#__PURE__*/_jsx(Toaster, {})] }) }); // All platforms use same wrapper (KeyboardProvider is passthrough on web) return /*#__PURE__*/_jsx(SafeAreaProvider, { children: /*#__PURE__*/_jsx(GestureHandlerRootView, { style: { flex: 1 }, children: /*#__PURE__*/_jsx(KeyboardProvider, { children: coreContent }) }) }); }; export default OxyProvider; //# sourceMappingURL=OxyProvider.js.map