npm package: @streambird/react-auth
Description: React authentication hooks and components for Streambird
Latest version: 0.2.39

# @streambird/react-auth

React hooks for interacting with Solana wallets through the Streambird authentication system. All hooks are designed to work seamlessly with both UI and headless modes.

## Installation

```bash
npm install @streambird/react-auth
```

## Quick Start

```typescript
import { StreambirdProvider } from '@streambird/react-auth';
import { useWallets, useSignMessage } from '@streambird/react-auth/solana';

function App() {
  return (
    <StreambirdProvider
      config={{
        publishableKey: 'your-publishable-key',
        config: {
          solana: {
            rpcs: {
              'solana:mainnet': {
                rpc: 'https://api.mainnet-beta.solana.com'
              }
            }
          }
        }
      }}
    >
      <MyComponent />
    </StreambirdProvider>
  );
}

function MyComponent() {
  const { wallets, loading } = useWallets();
  const { signMessage } = useSignMessage();

  const handleSign = async () => {
    const message = new TextEncoder().encode('Hello, Solana!');
    const result = await signMessage({
      message,
      wallet: wallets[0],
      options: { uiOptions: { showWalletUI: true } }
    });
    console.log('Signature:', result.signature);
  };

  if (loading) return <div>Loading...</div>;
  
  return (
    <div>
      <h1>My Wallets</h1>
      {wallets.map(wallet => (
        <div key={wallet.id}>{wallet.public_address}</div>
      ))}
      <button onClick={handleSign}>Sign Message</button>
    </div>
  );
}
```

## OAuth Authentication

### `useLoginWithOAuth()`

A dedicated hook for OAuth authentication that supports both callback-based and state-based approaches.

**Returns:**
```typescript
{
  state: {
    status: 'initial' | 'loading' | 'done' | 'error';
    error: Error | null;
    user: OAuthUser | null;
    isNewUser: boolean | null;
  };
  loading: boolean;
  initOAuth: (options: { provider: OAuthProvider }) => Promise<void>;
}
```

**State-based Usage:**
```typescript
import { useLoginWithOAuth } from '@streambird/react-auth';

function LoginWithOAuth() {
  const { state, loading, initOAuth } = useLoginWithOAuth();

  const handleLogin = async () => {
    try {
      // The user will be redirected to OAuth provider's login page
      await initOAuth({ provider: 'google' });
    } catch (err) {
      // Handle errors (network issues, validation errors, etc.)
      console.error(err);
    }
  };

  return (
    <div>
      <button onClick={handleLogin} disabled={loading}>
        {loading ? 'Logging in...' : 'Log in with Google'}
      </button>
      {state.status === 'done' && (
        <p>Welcome, {state.user?.name}!</p>
      )}
      {state.status === 'error' && (
        <p>Error: {state.error?.message}</p>
      )}
    </div>
  );
}
```

**Callback-based Usage:**
```typescript
import { useLoginWithOAuth } from '@streambird/react-auth';

function LoginWithOAuth() {
  const { initOAuth } = useLoginWithOAuth({
    onSuccess: ({ user, isNewUser }) => {
      console.log('User logged in successfully', user);
      if (isNewUser) {
        // Perform actions for new users
        console.log('Welcome new user!');
      }
    },
    onError: (error) => {
      console.error('Login failed', error);
    }
  });

  return (
    <button onClick={() => initOAuth({ provider: 'google' })}>
      Log in with Google
    </button>
  );
}
```

### `useStreambird()`

The main hook for authentication and flow control.

**Returns:**
```typescript
{
  // Authentication
  isAuthenticated: () => Promise<boolean>;
  getCurrentSession: () => Promise<any>;
  logout: () => Promise<LogoutResponse>;
  
  // Flow control
  loading: boolean;
  flowState: EmailOtpFlowState;
  start: () => Promise<any>;
  onError: (callback: (error: Error) => void) => void;
  
  // Metadata
  publishableKey: string;
}
```

**Supported OAuth Providers:**
- `google` - Google OAuth 2.0
- `github` - GitHub OAuth 2.0
- `discord` - Discord OAuth 2.0
- `twitter` - Twitter OAuth 2.0

## Solana Wallet Hooks

### `useWallets()`

Fetches and manages Solana wallets for the authenticated user.

**Returns:**
```typescript
{
  wallets: PublicWallet[];
  loading: boolean;
  error?: Error;
  refetch: () => void;
}
```

**Usage:**
```typescript
import { useWallets } from '@streambird/react-auth/solana';

function MyComponent() {
  const { wallets, loading, error, refetch } = useWallets();
  
  if (loading) return <div>Loading wallets...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <div>
      {wallets.map(wallet => (
        <div key={wallet.id}>{wallet.public_address}</div>
      ))}
      <button onClick={refetch}>Refresh</button>
    </div>
  );
}
```

### `useCreateWallet(callbacks?)`

Creates a new Solana wallet for the authenticated user.

**Parameters:**
```typescript
callbacks?: {
  onSuccess?: (result: { wallet: PublicWallet }) => void;
  onError?: (error: Error) => void;
}
```

**Returns:**
```typescript
{
  createWallet: (options?: {
    createAdditional?: boolean;
    walletIndex?: number;
  }) => Promise<{
    wallet: PublicWallet;
  }>;
}
```

**Usage:**
```typescript
import { useCreateWallet } from '@streambird/react-auth/solana';

function CreateWalletButton() {
  const { createWallet } = useCreateWallet({
    onSuccess: ({ wallet }) => {
      console.log('Wallet created:', wallet.public_address);
    },
    onError: (error) => {
      console.error('Failed to create wallet:', error);
    }
  });

  const handleCreate = async () => {
    try {
      const result = await createWallet();
      console.log('New wallet:', result.wallet);
    } catch (error) {
      console.error('Error:', error);
    }
  };

  return <button onClick={handleCreate}>Create Wallet</button>;
}
```

### `useSignMessage()`

Signs a message with the specified wallet.

**Returns:**
```typescript
{
  signMessage: (params: {
    message: Uint8Array;
    wallet: PublicWallet;
    options?: {
      uiOptions?: SignMessageUIOptions;
    };
  }) => Promise<{
    signature: Uint8Array;
  }>;
}
```

**Usage:**
```typescript
import { useSignMessage } from '@streambird/react-auth/solana';

function SignMessageButton() {
  const { signMessage } = useSignMessage();

  const handleSign = async () => {
    const message = new TextEncoder().encode('Hello, Solana!');
    const wallet = wallets[0]; // Your wallet
    
    try {
      const result = await signMessage({
        message,
        wallet,
        options: {
          uiOptions: {
            showWalletUI: true // Show UI modal
          }
        }
      });
      
      console.log('Signature:', result.signature);
    } catch (error) {
      console.error('Sign failed:', error);
    }
  };

  return <button onClick={handleSign}>Sign Message</button>;
}
```

### `useSignTransaction()`

Signs a transaction with the specified wallet.

**Returns:**
```typescript
{
  signTransaction: (params: {
    transaction: Uint8Array;
    wallet: PublicWallet;
    options?: SignTransactionOptions & {
      uiOptions?: SignTransactionUIOptions;
    };
  }) => Promise<{
    signedTransaction: Uint8Array;
  }>;
}
```

**Usage:**
```typescript
import { useSignTransaction } from '@streambird/react-auth/solana';

function SignTransactionButton() {
  const { signTransaction } = useSignTransaction();

  const handleSign = async () => {
    const transaction = new Uint8Array([...]); // Your transaction bytes
    const wallet = wallets[0]; // Your wallet
    
    try {
      const result = await signTransaction({
        transaction,
        wallet,
        options: {
          uiOptions: {
            showWalletUI: false // Headless mode
          }
        }
      });
      
      console.log('Signed transaction:', result.signedTransaction);
    } catch (error) {
      console.error('Sign failed:', error);
    }
  };

  return <button onClick={handleSign}>Sign Transaction</button>;
}
```

### `useSendTransaction()`

Sends a signed transaction to the Solana network.

**Returns:**
```typescript
{
  sendTransaction: (params: {
    transaction: Uint8Array;
    wallet: PublicWallet;
    chain?: SolanaChain;
    options?: SendTransactionOptions & {
      uiOptions?: SendTransactionUIOptions;
    };
  }) => Promise<{
    signature: Uint8Array;
  }>;
}
```

**Usage:**
```typescript
import { useSendTransaction } from '@streambird/react-auth/solana';

function SendTransactionButton() {
  const { sendTransaction } = useSendTransaction();

  const handleSend = async () => {
    const transaction = new Uint8Array([...]); // Your signed transaction
    const wallet = wallets[0]; // Your wallet
    
    try {
      const result = await sendTransaction({
        transaction,
        wallet,
        chain: 'solana:mainnet',
        options: {
          commitment: 'confirmed',
          uiOptions: {
            showWalletUI: true
          }
        }
      });
      
      console.log('Transaction signature:', result.signature);
    } catch (error) {
      console.error('Send failed:', error);
    }
  };

  return <button onClick={handleSend}>Send Transaction</button>;
}
```

### `useGetBalance()`

Fetches the balance of a Solana wallet.

**Returns:**
```typescript
{
  getBalance: (params: SolanaGetBalanceParams) => Promise<BaseGetBalanceResult>;
  loading: boolean;
  error: undefined;
}
```

**Parameters:**
```typescript
interface SolanaGetBalanceParams {
  wallet: PublicWallet;
  chain?: SolanaChain; // 'solana:mainnet' | 'solana:devnet' | 'solana:testnet'
  options?: {
    commitment?: TransactionCommitment;
    minContextSlot?: number;
  };
}
```

**Usage:**
```typescript
import { useGetBalance } from '@streambird/react-auth/solana';

function BalanceDisplay() {
  const { getBalance } = useGetBalance();
  const [balance, setBalance] = useState<string>('0');

  const fetchBalance = async () => {
    try {
      const result = await getBalance({
        wallet: wallets[0],
        chain: 'solana:mainnet',
        options: {
          commitment: 'finalized'
        }
      });
      
      setBalance(result.balanceNativeUnit);
    } catch (error) {
      console.error('Failed to get balance:', error);
    }
  };

  return (
    <div>
      <p>Balance: {balance} SOL</p>
      <button onClick={fetchBalance}>Refresh</button>
    </div>
  );
}
```

### `useGetTokenAccounts()`

Fetches token accounts for a Solana wallet.

**Returns:**
```typescript
{
  getTokenAccounts: (params: SolanaGetTokenAccountsParams) => Promise<SolanaGetTokenAccountsResult>;
  loading: boolean;
  error: undefined;
}
```

**Parameters:**
```typescript
interface SolanaGetTokenAccountsParams {
  wallet: PublicWallet;
  chain?: SolanaChain;
  mint?: string; // Specific mint address
  programId?: string; // SPL Token Program or Token-2022 Program
  options?: {
    commitment?: TransactionCommitment;
    minContextSlot?: number;
    dataSlice?: {
      offset: number;
      length: number;
    };
    encoding?: TransactionEncoding;
  };
}
```

**Usage:**
```typescript
import { useGetTokenAccounts } from '@streambird/react-auth/solana';

function TokenAccountsList() {
  const { getTokenAccounts } = useGetTokenAccounts();
  const [tokens, setTokens] = useState([]);

  const fetchTokens = async () => {
    try {
      const result = await getTokenAccounts({
        wallet: wallets[0],
        chain: 'solana:mainnet',
        programId: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', // SPL Token Program
        options: {
          commitment: 'finalized'
        }
      });
      
      setTokens(result.tokenAccounts);
    } catch (error) {
      console.error('Failed to get tokens:', error);
    }
  };

  return (
    <div>
      {tokens.map(token => (
        <div key={token.mint}>
          {token.symbol}: {token.uiAmountString}
        </div>
      ))}
      <button onClick={fetchTokens}>Load Tokens</button>
    </div>
  );
}
```

### `useExportKey()`

Exports the private key for a wallet (use with caution).

**Returns:**
```typescript
{
  exportKey: (params: { wallet: PublicWallet }) => Promise<any>;
}
```

**Usage:**
```typescript
import { useExportKey } from '@streambird/react-auth/solana';

function ExportKeyButton() {
  const { exportKey } = useExportKey();

  const handleExport = async () => {
    try {
      const key = await exportKey({ wallet: wallets[0] });
      console.log('Private key exported:', key);
      // Handle the exported key securely
    } catch (error) {
      console.error('Export failed:', error);
    }
  };

  return <button onClick={handleExport}>Export Key</button>;
}
```

## Configuration

The hooks require proper configuration in your Streambird provider:

```typescript
import { StreambirdProvider } from '@streambird/react-auth';

function App() {
  return (
    <StreambirdProvider
      config={{
        publishableKey: 'your-publishable-key',
        config: {
          solana: {
            rpcs: {
              'solana:mainnet': {
                rpc: 'https://api.mainnet-beta.solana.com'
              },
              'solana:devnet': {
                rpc: 'https://api.devnet.solana.com'
              },
              'solana:testnet': {
                rpc: 'https://api.testnet.solana.com'
              }
            }
          }
        }
      }}
    >
      {/* Your app */}
    </StreambirdProvider>
  );
}
```

## UI vs Headless Mode

Most hooks support both UI and headless modes through the `uiOptions` parameter:

- **UI Mode**: Shows a modal with wallet interface
- **Headless Mode**: Performs the action without UI

```typescript
// UI Mode
await signMessage({
  message,
  wallet,
  options: {
    uiOptions: { showWalletUI: true }
  }
});

// Headless Mode
await signMessage({
  message,
  wallet,
  options: {
    uiOptions: { showWalletUI: false }
  }
});
```

## Error Handling

All hooks throw errors that should be caught and handled appropriately:

```typescript
try {
  const result = await signMessage({ message, wallet });
} catch (error) {
  if (error.message.includes('User rejected')) {
    // Handle user rejection
  } else if (error.message.includes('Network error')) {
    // Handle network issues
  } else {
    // Handle other errors
  }
}
```

## TypeScript Support

This package is written in TypeScript and provides full type definitions. All parameters and return types are properly typed for better development experience.

## License

MIT