npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ocid-connect-react-native

v1.0.11

Published

OCID Connector Library for React Native

Readme

OCID Connect React Native SDK - Expo Go Integration Guide

A comprehensive guide for integrating the OCID Connect React Native SDK in an Expo Go application, including complete setup, configuration, and implementation examples.

Table of Contents

  1. Prerequisites
  2. Installation
  3. Expo Configuration
  4. Project Setup
  5. Components Implementation
  6. Authentication Flow
  7. Complete Example
  8. Troubleshooting

Prerequisites

Required Accounts and IDs

  • Sandbox Mode (Development): No Client ID required - perfect for testing and development

Installation

Install the OCID Connect React Native SDK and required dependencies:

1. Install the SDK

# Install the main SDK
npm install ocid-connect-react-native

# Or with yarn
yarn add ocid-connect-react-native

2. Install peer dependencies

# Required dependencies (Expo-managed)
npx expo install \
  @react-native-async-storage/async-storage \
  react-native-get-random-values \
  expo-crypto \
  expo-web-browser \
  expo-auth-session \
  expo-linking

# Pure JS deps (regular install is fine)
npm install base-64 jwt-decode
# or
yarn add base-64 jwt-decode
`

Expo Configuration

1. Configure app.json

Update your app.json to include the custom URL scheme for deep linking:

{
  "expo": {
    "name": "your-app-name",
    "slug": "your-app-slug",
    "version": "1.0.0",
    "scheme": "yourapp",
    "linking": {
      //Add prefix
      "prefixes": [
        "yourapp://"
      ]
    },
    "ios": {
      "supportsTablet": true
    },
    "android": {
      "adaptiveIcon": {
        "backgroundColor": "#E6F4FE"
      }
    },
    "plugins": [
      "expo-router"
    ]
  }
}

Important: The scheme must match your redirect URI configuration.

Project Setup

1. Import Required Polyfills

At the very top of your app's entry point (usually _layout.tsx or App.js ), import the required polyfills:

import 'react-native-get-random-values';

2. Define Configuration Constants

Create a configuration file or define constants for your OCID setup:

// constants/ocid.ts
export const OCID_CONFIG = {
  CLIENT_ID: 'sandbox', // Use 'sandbox' for development, your actual client ID for production
  REDIRECT_URI: 'yourapp://auth/callback', // Must match your app.json scheme
  SANDBOX_MODE: true, // Set to false for production
};

Components Implementation

1. Create OCID Components Directory

Create the following component files in your components/ocid/ directory:

components/ocid/OCContext.tsx

import * as React from 'react';

interface OCAuthContextType {
  OCId?: string;
  ethAddress?: string;
  ocAuth?: any;
  authState?: any;
  authError?: any;
  isInitialized: boolean;
  setAuthError: (error: any) => void;
}

export const OCContext = React.createContext<OCAuthContextType | null>(null);

export const useOCAuth = (): OCAuthContextType => {
  const context = React.useContext(OCContext);
  if (!context) {
    throw new Error('useOCAuth must be used within an OCConnect provider');
  }
  return context;
};

components/ocid/OCConnect.tsx

import React, { useEffect, useState } from 'react';
import { OCAuthLive, OCAuthSandbox } from 'ocid-connect-react-native';
import { OCContext } from './OCContext';

interface OCConnectProps {
    children: React.ReactNode;
    opts: any;
    sandboxMode?: boolean;
}

const OCConnect: React.FC<OCConnectProps> = ({ children, opts, sandboxMode }) => {
    const [ocAuth, setOcAuth] = useState<any>();
    const [OCId, setOCId] = useState<string>();
    const [ethAddress, setEthAddress] = useState<string>();
    const [authState, setAuthState] = useState<any>();
    const [isInitialized, setIsInitialized] = useState(false);
    const [authError, setAuthError] = useState(null);

    const updateAuthState = (authState: any) => {
        setAuthState(authState);
        setOCId(authState.OCId);
        setEthAddress(authState.ethAddress);
    };

    // Initialize SDK
    useEffect(() => {
        const initAuth = async () => {
            const authSdk = sandboxMode ? new OCAuthSandbox(opts) : new OCAuthLive(opts);
            await authSdk.initialize();
            updateAuthState(authSdk.getAuthState());
            setOcAuth(authSdk);
            setIsInitialized(true);
        };
        
        initAuth();
    }, [opts, sandboxMode]);

    useEffect(() => {
        if (ocAuth && ocAuth.authInfoManager) {
            // Reactively receive updates on auth state changes
            ocAuth.authInfoManager.subscribe(updateAuthState);
            return () => {
                ocAuth.authInfoManager.unsubscribe(updateAuthState);
            };
        }
    }, [ocAuth]);

    return (
        <OCContext.Provider value={{ OCId, ethAddress, ocAuth, authState, authError, isInitialized, setAuthError }}>
            {children}
        </OCContext.Provider>
    );
};

export default OCConnect;

components/ocid/LoginButton.tsx

import React from 'react';
import { TouchableOpacity, Text, Image, StyleSheet } from 'react-native';
import { useOCAuth } from './OCContext';

const themes = {
    light: {
        backgroundColor: "#FFF",
        borderColor: "#DDDDEB", 
        textColor: "#C5C5D1",
    },
    dark: {
        backgroundColor: "#141414",
        borderColor: "#DDDDEB",
        textColor: "#FFFFFF",
    },
    neutral: {
        backgroundColor: "#F5F5F5",
        borderColor: "#DDDDEB",
        textColor: "#141414",
    },
    ocBlue: {
        backgroundColor: "#141BEB",
        borderColor: "#DDDDEB",
        textColor: "#FFFFFF",
    },
};

interface LoginButtonProps {
    pill?: boolean;
    disabled?: boolean;
    theme?: keyof typeof themes;
    state?: string;
    emailPlaceholder?: string;
}

export default function LoginButton({ pill, disabled, theme = 'ocBlue', state, emailPlaceholder }: LoginButtonProps) {
    const { ocAuth } = useOCAuth();
    const selectedTheme = themes[theme] || themes.ocBlue;

    const loginWithRedirect = async () => {
        if (ocAuth) {
            try {
                await ocAuth.signInWithRedirect({ state, emailPlaceholder });
            } catch (error: any) {
                if (error.message.includes('SANDBOX_VERIFICATION_REQUIRED')) {
                    // Handle sandbox verification error gracefully
                    console.warn('Sandbox verification required:', error.message);
                } else {
                    throw error;
                }
            }
        }
    };

    const buttonStyle = [
        styles.button,
        {
            backgroundColor: selectedTheme.backgroundColor,
            borderColor: selectedTheme.borderColor,
            borderRadius: pill ? 22 : 8,
        },
        disabled && styles.disabled
    ];

    return (
        <TouchableOpacity
            style={buttonStyle}
            onPress={loginWithRedirect}
            disabled={disabled}
        >
            <Image 
                source={{ uri: 'https://static.opencampus.xyz/assets/oc_logo.svg' }}
                style={styles.logo}
            />
            <Text style={[styles.text, { color: selectedTheme.textColor }]}>
                Connect <Text style={styles.bold}>OCID</Text>
            </Text>
        </TouchableOpacity>
    );
}

const styles = StyleSheet.create({
    button: {
        flexDirection: 'row',
        alignItems: 'center',
        height: 44,
        width: 160,
        paddingHorizontal: 12,
        borderWidth: 1,
        justifyContent: 'center',
    },
    disabled: {
        opacity: 0.6,
    },
    logo: {
        width: 26,
        height: 25,
        marginRight: 10,
    },
    text: {
        fontSize: 14,
    },
    bold: {
        fontWeight: 'bold',
    },
});

components/ocid/OCSpinner.tsx

import React from 'react';
import { ActivityIndicator, StyleSheet, View } from 'react-native';

interface OCSpinnerProps {
    height?: number;
    width?: number;
    size?: 'small' | 'large';
    color?: string;
}

const OCSpinner: React.FC<OCSpinnerProps> = ({ 
    height = 50, 
    width = 50, 
    size = 'large',
    color = '#141BEB'
}) => {
    return (
        <View style={[styles.container, { height, width }]}>
            <ActivityIndicator size={size} color={color} />
        </View>
    );
};

const styles = StyleSheet.create({
    container: {
        justifyContent: 'center',
        alignItems: 'center',
    },
});

export default OCSpinner;

components/ocid/LoginCallBack.tsx

import React, { useEffect, useRef } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { useOCAuth } from './OCContext';
import OCSpinner from './OCSpinner';

interface LoginCallBackProps {
    url: string;
    successCallback?: () => void;
    errorCallback?: (error: any) => void;
    customErrorComponent?: React.ReactNode;
    customLoadingComponent?: React.ReactNode;
}

const LoginCallBack: React.FC<LoginCallBackProps> = ({ 
    url, 
    successCallback, 
    errorCallback, 
    customErrorComponent, 
    customLoadingComponent 
}) => {
    const { isInitialized, ocAuth, authState, setAuthError } = useOCAuth();
    const handledRedirectRef = useRef(false);

    useEffect(() => {
        const handleLogin = async () => {
            if (ocAuth && url && !handledRedirectRef.current) {
                handledRedirectRef.current = true;
                try {
                    console.log('Handling login redirect with URL:', url);
                    await ocAuth.handleLoginRedirect(url);
                    console.log('Login redirect handled successfully');
                    if (successCallback) successCallback();
                } catch (e) {
                    console.error('Login redirect error:', e);
                    setAuthError(e);
                    if (errorCallback) {
                        errorCallback(e);
                    }
                }
            }
        };
        
        if (isInitialized && ocAuth && url) {
            handleLogin();
        }
    }, [ocAuth, url, successCallback, errorCallback, setAuthError, isInitialized]);

    // Show error if there's an auth error and no custom error callback
    if (isInitialized && authState?.error !== undefined && !errorCallback) {
        return customErrorComponent ? customErrorComponent : (
            <View style={styles.container}>
                <Text style={styles.errorText}>Error Logging in: {authState.error.message}</Text>
            </View>
        );
    }
    
    // Show success message if authenticated
    if (isInitialized && authState?.isAuthenticated) {
        return (
            <View style={styles.container}>
                <Text style={styles.successText}>Login successful! Redirecting...</Text>
                {customLoadingComponent ? customLoadingComponent : <OCSpinner height={100} width={100} />}
            </View>
        );
    }
    
    // Default loading state
    return customLoadingComponent ? (
        customLoadingComponent
    ) : (
        <View style={styles.container}>
            <OCSpinner height={100} width={100} />
        </View>
    );
};

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
    },
    errorText: {
        fontSize: 16,
        color: 'red',
        textAlign: 'center',
        paddingHorizontal: 20,
    },
    successText: {
        fontSize: 16,
        color: 'green',
        textAlign: 'center',
        paddingHorizontal: 20,
        marginBottom: 20,
    },
});

export default LoginCallBack;

components/ocid/index.tsx

export { default as OCConnect } from './OCConnect';
export { default as LoginButton } from './LoginButton';
export { default as LoginCallBack } from './LoginCallBack';
export { default as OCSpinner } from './OCSpinner';
export { OCContext, useOCAuth } from './OCContext';

API Reference

OCAuth Methods

| Method | Description | | --- | --- | | signInWithRedirect | Initialize login process. Accepts state & emailPlaceholder as optional inputs | | handleLoginRedirect | Return the auth state of the login process | | getAuthState | Return auth state data { accessToken, idToken, OCId, ethAddress, isAuthenticated } | | logout | Clear authentication state and log out the user |

signInWithRedirect Options

await ocAuth.signInWithRedirect({
  state: 'your-custom-state',           // Optional: Custom state parameter
  emailPlaceholder: '[email protected]'  // Optional: Pre-fill email in login form
});

LoginButton Props

| Prop | Type | Default | Description | | --- | --- | --- | --- | | pill | boolean | false | Use pill-shaped button style | | disabled | boolean | false | Disable the button | | theme | 'light' \| 'dark' \| 'neutral' \| 'ocBlue' | 'ocBlue' | Button color theme | | state | string | undefined | Custom state to pass to auth flow | | emailPlaceholder | string | undefined | Pre-fill email address in login form |

Example with email placeholder:

<LoginButton
  theme="ocBlue"
  pill={true}
  state={JSON.stringify({ timestamp: Date.now() })}
  emailPlaceholder='[email protected]'
/>

Authentication Flow

1. App Root Setup

Wrap your entire app with the OCConnect provider in your main app component:

// App.tsx or _layout.tsx (for Expo Router)
import React from 'react';
import { OCConnect } from '@/components/ocid';
import { OCID_CONFIG } from '@/constants/ocid';

export default function App() {
  const opts = {
    clientId: OCID_CONFIG.CLIENT_ID,
    redirectUri: OCID_CONFIG.REDIRECT_URI,
  };

  return (
    <OCConnect opts={opts} sandboxMode={OCID_CONFIG.SANDBOX_MODE}>
      {/* Your app components */}
      <YourAppContent />
    </OCConnect>
  );
}

2. Authentication Screen Implementation

Create a complete authentication screen:

// app/(tabs)/auth.tsx
import React from 'react';
import { View, StyleSheet, Alert, ScrollView, Text, TouchableOpacity } from 'react-native';
import { LoginButton, useOCAuth } from '@/components/ocid';

function AuthContent() {
  const { isInitialized, ocAuth, authState } = useOCAuth();

  const handleLogout = async () => {
    if (ocAuth) {
      try {
        await ocAuth.logout();
        Alert.alert('Success', 'You have been logged out successfully', [{ text: 'OK' }]);
      } catch (error) {
        console.error('Logout failed:', error);
        Alert.alert('Error', 'Logout failed');
      }
    }
  };

  const isAuthenticated = authState?.isAuthenticated || false;

  // Loading state
  if (!isInitialized) {
    return (
      <View style={styles.container}>
        <Text>Initializing OCID Connect...</Text>
      </View>
    );
  }

  return (
    <ScrollView style={styles.container}>
      <View style={styles.content}>
        <Text style={styles.title}>OCID Connect Demo</Text>
        
        <View style={styles.section}>
          <Text style={styles.subtitle}>Authentication Status</Text>
          <View style={[styles.statusBadge, isAuthenticated ? styles.authenticated : styles.unauthenticated]}>
            <Text style={[styles.statusText, isAuthenticated ? styles.authenticatedText : styles.unauthenticatedText]}>
              {isAuthenticated ? 'Authenticated' : 'Not Authenticated'}
            </Text>
          </View>
        </View>

        {isAuthenticated && authState ? (
          <View style={styles.section}>
            <Text style={styles.subtitle}>User Information</Text>
            <View style={styles.userInfo}>
              <Text style={styles.infoText}>OCID: {authState.OCId || 'N/A'}</Text>
              <Text style={styles.infoText}>Eth Address: {authState.ethAddress || 'N/A'}</Text>
              <Text style={styles.infoText}>Authenticated: {authState.isAuthenticated ? 'Yes' : 'No'}</Text>
            </View>
            
            <TouchableOpacity onPress={handleLogout} style={styles.logoutButton}>
              <Text style={styles.logoutText}>Logout</Text>
            </TouchableOpacity>
          </View>
        ) : (
          <View style={styles.section}>
            <Text style={styles.subtitle}>Get Started</Text>
            <Text style={styles.instructions}>
              Tap the login button below to start the OCID authentication flow.
            </Text>
            
            <View style={styles.buttonContainer}>
              <LoginButton
                theme="ocBlue"
                pill={true}
                disabled={false}
                state={JSON.stringify({ timestamp: Date.now() })}
                emailPlaceholder='[email protected]'
              />
            </View>
          </View>
        )}
      </View>
    </ScrollView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  content: {
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    textAlign: 'center',
    marginBottom: 20,
  },
  section: {
    marginBottom: 24,
    padding: 16,
    backgroundColor: 'white',
    borderRadius: 12,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  subtitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 8,
  },
  statusBadge: {
    paddingHorizontal: 12,
    paddingVertical: 8,
    borderRadius: 8,
    alignSelf: 'flex-start',
    marginTop: 8,
  },
  authenticated: {
    backgroundColor: '#e8f5e8',
  },
  unauthenticated: {
    backgroundColor: '#ffeaea',
  },
  statusText: {
    fontWeight: 'bold',
    fontSize: 14,
  },
  authenticatedText: {
    color: '#2d5a2d',
  },
  unauthenticatedText: {
    color: '#8b2635',
  },
  userInfo: {
    marginTop: 12,
    padding: 12,
    backgroundColor: '#f8f9fa',
    borderRadius: 8,
  },
  infoText: {
    fontSize: 14,
    marginBottom: 4,
    color: '#495057',
  },
  instructions: {
    marginBottom: 16,
    lineHeight: 18,
    color: '#666',
  },
  buttonContainer: {
    alignItems: 'center',
    marginTop: 16,
  },
  logoutButton: {
    backgroundColor: '#dc3545',
    paddingHorizontal: 24,
    paddingVertical: 12,
    borderRadius: 8,
    marginTop: 16,
    alignItems: 'center',
  },
  logoutText: {
    color: 'white',
    fontWeight: 'bold',
  },
});

3. Add Auth Tab to Navigation (Expo Router)

If you're using Expo Router with tab navigation, you need to add the Auth screen to your tab layout.

Step 1: Save the Auth Screen in the Right Location

Step 2: Update Your Tab Layout

Add the Auth tab to your app/(tabs)/_layout.tsx:

// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
import React from 'react';
import { IconSymbol } from '@/components/ui/icon-symbol';
import { Colors } from '@/constants/theme';
import { useColorScheme } from '@/hooks/use-color-scheme';

export default function TabLayout() {
  const colorScheme = useColorScheme();

  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
        headerShown: false,
      }}>
      <Tabs.Screen
        name="index"
        options={{
          title: 'Home',
          tabBarIcon: ({ color }) => <IconSymbol size={28} name="house.fill" color={color} />,
        }}
      />
      <Tabs.Screen
        name="explore"
        options={{
          title: 'Explore',
          tabBarIcon: ({ color }) => <IconSymbol size={28} name="paperplane.fill" color={color} />,
        }}
      />
      {/* Add the Auth tab */}
      <Tabs.Screen
        name="auth"
        options={{
          title: 'Auth',
          tabBarIcon: ({ color }) => <IconSymbol size={28} name="person.circle.fill" color={color} />,
        }}
      />
    </Tabs>
  );
}

Note: If you don't have IconSymbol component, you can use any icon library (like @expo/vector-icons) or a simple Text component for the icon.

What You Should See

After completing these steps, your app should have:

  • ✅ Three tabs visible at the bottom: Home, Explore, and Auth
  • ✅ Tapping the Auth tab shows your authentication screen
  • ✅ The screen displays the login button when not authenticated
  • ✅ The screen displays user info and logout button when authenticated

4. Deep Link Handling (Expo Router)

// app/auth/callback.tsx
import { useLocalSearchParams, useRouter } from 'expo-router';
import React from 'react';
import { LoginCallBack } from '../../components/ocid';

export default function AuthCallback() {
  const params = useLocalSearchParams();
  const router = useRouter();
  
  const url = `yourapp://auth/callback?${new URLSearchParams(params as Record<string, string>).toString()}`;

  const handleSuccess = () => {
    console.log('Auth callback success - navigating to home');
    // Use push instead of replace to ensure proper navigation
    router.push('/');
  };

  const handleError = (error: any) => {
    console.error('Auth callback error:', error);
    // Navigate back to home even on error to prevent getting stuck
    setTimeout(() => {
      router.push('/');
    }, 2000);
  };

  return (
    <LoginCallBack
      url={url}
      successCallback={handleSuccess}
      errorCallback={handleError}
    />
  );
}

Production Setup

1. Open Campus Developer Portal Setup

  1. Log in to the Open Campus Developer Portal

  2. Navigate to your application settings

  3. Add your production callback URL to the Authorized Redirect URIs:

    yourapp://auth/callback

    Replace yourapp with your actual app scheme from app.json

  4. Save your changes and copy your Client ID

2. Update Frontend Configuration

Update your configuration file with your production credentials:

// constants/ocid.ts
export const OCID_CONFIG = {
  CLIENT_ID: '<your-client-id-from-developer-portal>', // Replace with your actual client ID
  REDIRECT_URI: 'yourapp://auth/callback', // Must match your app.json scheme
  SANDBOX_MODE: false, // Set to false for production
};

Important Notes:

  • Replace <your-client-id-from-developer-portal> with your actual Client ID from the Open Campus Developer Portal
  • Ensure the REDIRECT_URI matches exactly what you configured in the developer portal
  • The redirect URI scheme (yourapp://) must match the scheme field in your app.json