# React Native / Expo support - `@bounded-sh/client`

The SDK ships a dedicated React Native entry (`react-native` condition ->
`dist/index.native.js`) and a platform-abstraction layer so the same
`@bounded-sh/client` package runs on web and React Native.

## 1. One-time setup before `init()`

React Native has no `localStorage`, no `document`, and no reliable browser
base64 helpers, so configure the SDK platform once at app startup:

```ts
import "react-native-get-random-values";
import "react-native-url-polyfill/auto";
import { setPlatform, ReactNativeSessionManager } from "@bounded-sh/client";
import { createMMKV } from "react-native-mmkv";
import { decode as atob, encode as btoa } from "base-64";

const store = createMMKV();
const storage = {
  getItem: (key: string) => store.getString(key) ?? null,
  setItem: (key: string, value: string) => store.set(key, value),
  removeItem: (key: string) => store.remove(key),
};

setPlatform({ storage, sessionStorage: storage, atob, btoa, hasDOM: false });
ReactNativeSessionManager.configure({ storage, atob });
```

`setPlatform` fills general browser API gaps. `ReactNativeSessionManager.configure`
selects the RN token store used by login, restore, logout, and authenticated
requests.

## 2. Hosted email / social login

Human login runs through the hosted Bounded issuer. Retired app-origin OTP
helpers (`sendEmailOtp`, `verifyEmailOtp`, `sendTextOtp`, `verifyTextOtp`) are
not exported by the package.

Install the Expo optional peers if your RN app uses hosted login:

```sh
npx expo install expo-web-browser expo-crypto
```

Use an https universal link as your redirect URI and register that origin in the
Bounded app's `allowedOrigins`:

```ts
import { init, loginWithRedirect, getCurrentUser } from "@bounded-sh/client";

await init({ appId: "YOUR_APP_ID" });

const user = await loginWithRedirect({
  redirectUri: "https://yourapp.com/auth/callback",
  methods: ["email"],
});

const restored = await getCurrentUser();
```

On native, `loginWithRedirect()` opens the system/in-app browser and resolves
with the user when the callback returns to the app. `completeLoginFromRedirect()`
is for web callback pages.

## 3. Guest login

Anonymous auth is fully native and does not require Expo browser packages:

```ts
import { init, signInAnonymously, getCurrentUser } from "@bounded-sh/client";

await init({ appId: "YOUR_APP_ID" });
await signInAnonymously();
const user = await getCurrentUser();
```

The device-local ed25519 keypair is stored in the RN storage adapter, so the
guest identity survives app restarts until `logout()` clears it.

## 4. Privy on React Native (`privy-expo`)

`@privy-io/expo` is hook-based, so the host app renders `<PrivyProvider>` and
bridges the hooks into a `PrivyExpoProvider` instance.

```bash
npm i @privy-io/expo @privy-io/expo-native-extensions
```

```tsx
import { PrivyProvider, usePrivy, useEmbeddedSolanaWallet, useIdentityToken } from "@privy-io/expo";
import { PrivyExpoProvider, init, loginWithPrivy } from "@bounded-sh/client";

const provider = new PrivyExpoProvider(PRIVY_APP_ID, SOLANA_RPC_URL);

function PrivyBridge() {
  const privy = usePrivy();
  const wallet = useEmbeddedSolanaWallet();
  const { getIdentityToken } = useIdentityToken();

  useEffect(() => {
    provider.setPrivyMethods({
      isReady: privy.isReady,
      isAuthenticated: !!privy.user,
      user: privy.user,
      login: privy.login,
      logout: privy.logout,
      getAccessToken: privy.getAccessToken,
      getIdentityToken,
      getWalletProvider: async () => {
        const solanaWallet = wallet.wallets?.[0];
        if (!solanaWallet) return null;
        const solanaProvider = await solanaWallet.getProvider();
        return {
          address: solanaWallet.address,
          signMessage: solanaProvider.signMessage,
          signTransaction: solanaProvider.signTransaction,
          signAndSendTransaction: solanaProvider.signAndSendTransaction,
        };
      },
    });
  }, [privy.isReady, privy.user, wallet.wallets]);

  return null;
}

await init({
  appId,
  authMethod: "privy-expo",
  privyExpoProvider: provider,
  rpcUrl: SOLANA_RPC_URL,
});

await loginWithPrivy();
```

## 5. Metro notes

The default client entry no longer imports Phantom, web Privy, mobile wallet
adapter, or `react-dom` provider implementations. Apps that only use hosted login
or guest login should not need Metro stubs for those optional peers.

For Solana signing paths, keep `react-native-get-random-values` at app entry and
make sure `buffer` is polyfilled if your bundler setup does not provide it.
