React Native

Seamless OZZOBiT integration for React Native applications

Integrate OZZOBiT into your React Native application using WebView. This guide covers both Expo and bare React Native projects.

Installation

Install WebView Dependency

bashbash
npx expo install react-native-webview
bashbash
npm install react-native-webview
# For React Native < 0.60:
cd ios && pod install && cd ..

Implementation

Create OZZOBiT Component

OZZOBiTWidget.tsxtsx
// components/OZZOBiTWidget.tsx
import React, { useRef, useCallback } from 'react'
import {
  View,
  StyleSheet,
  Platform,
  BackHandler,
  NativeEventEmitter,
  NativeModules,
  DeviceEventEmitter,
} from 'react-native'
import { WebView, WebViewNavigation } from 'react-native-webview'

interface OZZOBiTConfig {
  apiKey: string
  environment?: 'STAGING' | 'PRODUCTION'
  productsAvailed?: string
  network?: string
  defaultCryptoCurrency?: string
  defaultFiatCurrency?: string
  walletAddress?: string
  fiatAmount?: number
  cryptoAmount?: number
  email?: string
  themeColor?: string
  redirectURL?: string
}

interface OZZOBiTProps {
  config: OZZOBiTConfig
  onSuccess?: (data: any) => void
  onFailure?: (data: any) => void
  onClose?: () => void
}

const OZZOBiTWidget: React.FC<OZZOBiTProps> = ({
  config,
  onSuccess,
  onFailure,
  onClose,
}) => {
  const webViewRef = useRef<WebView>(null)

  const buildUrl = useCallback(() => {
    const params = new URLSearchParams({
      apiKey: config.apiKey,
      environment: config.environment || 'PRODUCTION',
      productsAvailed: config.productsAvailed || 'BUY',
      network: config.network || 'ethereum',
      defaultCryptoCurrency: config.defaultCryptoCurrency || 'ETH',
      defaultFiatCurrency: config.defaultFiatCurrency || 'USD',
      themeColor: config.themeColor || '#1461db',
    })

    if (config.walletAddress) params.set('walletAddress', config.walletAddress)
    if (config.fiatAmount) params.set('fiatAmount', String(config.fiatAmount))
    if (config.cryptoAmount) params.set('cryptoAmount', String(config.cryptoAmount))
    if (config.email) params.set('email', config.email)
    if (config.redirectURL) params.set('redirectURL', config.redirectURL)

    return `https://OZZOBiT.com/global?${params.toString()}`
  }, [config])

  const handleNavigationStateChange = useCallback(
    (navState: WebViewNavigation) => {
      const url = navState.url

      // Handle callback URLs
      if (url?.includes('callback') || url?.includes('status=')) {
        try {
          const urlObj = new URL(url)
          const status = urlObj.searchParams.get('status')
          const orderId = urlObj.searchParams.get('orderId')

          switch (status) {
            case 'SUCCESS':
              onSuccess?.({ orderId, status })
              break
            case 'FAILURE':
              onFailure?.({ orderId, status })
              break
          }
        } catch (e) {
          console.error('Error parsing callback URL:', e)
        }
      }
    },
    [onSuccess, onFailure]
  )

  // Handle Android back button to navigate within WebView
  useEffect(() => {
    const backHandler = BackHandler.addEventListener(
      'hardwareBackPress',
      () => {
        if (webViewRef.current?.canGoBack()) {
          webViewRef.current.goBack()
          return true
        }
        onClose?.()
        return false
      }
    )

    return () => backHandler.remove()
  }, [onClose])

  // Inject JavaScript for event handling
  const injectedJavaScript = `
    window.ReactNativeWebView = window.ReactNativeWebView || {};
    
    // Listen for postMessage events from OZZOBiT
    window.addEventListener('message', function(event) {
      if (event.data && event.data.eventName) {
        window.ReactNativeWebView.postMessage(JSON.stringify(event.data));
      }
    });
    
    // Override postMessage for compatibility
    const originalPostMessage = window.postMessage;
    window.postMessage = function(data) {
      if (data && data.eventName) {
        window.ReactNativeWebView.postMessage(JSON.stringify(data));
      }
      originalPostMessage.apply(this, arguments);
    };
    
    true; // Required for injected JS
  `

  const handleMessage = useCallback((event: any) => {
    try {
      const data = JSON.parse(event.nativeEvent.data)
      
      switch (data.eventName) {
        case 'ORDER_SUCCESSFUL':
          onSuccess?.(data)
          break
        case 'ORDER_FAILED':
          onFailure?.(data)
          break
        case 'CLOSE_WIDGET':
          onClose?.()
          break
        default:
          console.log('[OZZOBiT Event]', data.eventName, data)
      }
    } catch (e) {
      console.error('Error parsing message:', e)
    }
  }, [onSuccess, onFailure, onClose])

  return (
    <View style={styles.container}>
      <WebView
        ref={webViewRef}
        source={{ uri: buildUrl() }}
        style={styles.webview}
        originWhelist={['*']}
        onNavigationStateChange={handleNavigationStateChange}
        onMessage={handleMessage}
        injectedJavaScriptBeforeContentLoaded={injectedJavaScript}
        javaScriptEnabled={true}
        domStorageEnabled={true}
        allowsInlineMediaPlayback={true}
        startInLoadingState={true}
        renderLoading={() => (
          <View style={styles.loadingContainer}>
            {/* Add your loading indicator here */}
          </View>
        )}
        {...(Platform.OS === 'android' ? {
          mixedContentMode: 'always_allow',
        } : {})}
      />
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  webview: {
    flex: 1,
  },
  loadingContainer: {
    ...StyleSheet.absoluteFillObject,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#fff',
  },
})

export default OZZOBiTWidget

Use in Your App

BuyCryptoScreen.tsxtsx
// screens/BuyCryptoScreen.tsx
import React, { useState } from 'react'
import { View, Button, Alert, Modal } from 'react-native'
import OZZOBiTWidget from '../components/OZZOBiTWidget'

export default function BuyCryptoScreen() {
  const [showWidget, setShowWidget] = useState(false)

  return (
    <View style={{ flex: 1, justifyContent: 'center', padding: 20 }}>
      <Button
        title="Buy Crypto with OZZOBiT"
        onPress={() => setShowWidget(true)}
      />

      <Modal visible={showWidget} animationType="slide">
        <OZZOBiTWidget
          config={{
            apiKey: process.env.EXPO_PUBLIC_OZZOBiT_API_KEY!,
            environment: __DEV__ ? 'STAGING' : 'PRODUCTION',
            productsAvailed: 'BUY',
            network: 'ethereum',
            defaultCryptoCurrency: 'ETH',
            walletAddress: '0x742d35Cc6634C0532925a3b844Bc9e7595f8bD21',
          }}
          onSuccess={(data) => {
            Alert.alert('Success!', `Order ID: ${data.orderId}`)
            setShowWidget(false)
          }}
          onFailure={(data) => {
            Alert.alert('Failed', `Order failed. ID: ${data.orderId}`)
            setShowWidget(false)
          }}
          onClose={() => setShowWidget(false)}
        />
      </Modal>
    </View>
  )
}
⚠️
Platform-Specific Notes
  • iOS: Ensure you've added NSAppTransportSecurity exceptions in Info.plist for OZZOBiT domains
  • Android: Set usesCleartextTraffic only if needed during development; use HTTPS in production
  • Expo: Use npx expo install react-native-webview instead of npm for better compatibility
  • The originWhitelist=* is needed for postMessage communication