npm package: @siymo/otp-sdk-react
Description: React hooks SDK for inbound call and inbound SMS OTP flows built on top of @siymo/otp-sdk-core
Latest version: 0.1.0

# siymo-otp-react-sdk

React hooks SDK for inbound OTP client flows, powered by `@siymo/otp-sdk-core`.

## What It Provides

- `useInboundCallOtp()` for inbound call OTP sessions
- `useInboundSmsOtp()` for inbound SMS OTP sessions
- Build-time or browser default base URL resolution
- Live WebSocket events for attempts, lock, verification, and expiry
- Optional long-poll mode if a React client prefers HTTP waiting

## Install

```bash
npm install siymo-otp-react-sdk @siymo/otp-sdk-core react
```

## Default Behavior

If you do not pass a `baseUrl`, the hooks resolve it like this:

1. Build-time env value from `SIYMO_OTP_REACT_BASE_URL`
2. Build-time env value from `SIYMO_OTP_BASE_URL`
3. Build-time env value from `OTP_BASE_URL`
4. Build-time env value from `baseURL`
5. Browser `location.origin`
6. Fallback to `http://localhost:3000`

That lets you bake a service URL into the built package, while still keeping same-origin React apps working without extra configuration.

To build the package with a default service URL:

```bash
SIYMO_OTP_REACT_BASE_URL=http://localhost:3000 npm run build
```

## Basic Usage

```tsx
import { useInboundSmsOtp } from 'siymo-otp-react-sdk';

export function InboundSmsWidget() {
  const { data, error, loading, create, eventLog } = useInboundSmsOtp({
    onTried(event) {
      console.log('Try number:', event.data.attempts);
    },
    onSuccess() {
      console.log('OTP verified');
    },
  });

  async function handleStart() {
    await create({
      phone: '+998990649000',
      expiration: 60,
      maxTries: 3,
      qrCode: true,
    });
  }

  return (
    <div>
      <button onClick={handleStart}>Create session</button>
      <p>Loading: {loading ? 'yes' : 'no'}</p>
      <p>Status: {data.status}</p>
      <p>Attempts: {data.attempts}</p>
      <p>Tries left: {data.triesLeft ?? '-'}</p>
      <p>Decoded SMS: {data.decodedText ?? '-'}</p>
      {data.qrCodeImage ? <img src={data.qrCodeImage} alt="Inbound SMS QR code" width={180} /> : null}
      <p>Latest event: {eventLog.at(-1)?.description ?? '-'}</p>
      {error ? <p>{error.message}</p> : null}
    </div>
  );
}
```

## Hook Return Shape

Both hooks expose:

- `data` as the primary, flattened payload for the hook
- `loading`, `error`, `message`, and `called` for easy UI state
- `create(request)`, `restart(request?)`, `wait(sessionId?)`, `stop()`, and `clear()`
- Legacy aliases are still available: `start`, `waitForSession`, `cancel`, `reset`
- `eventLog` with pre-described entries ready for rendering
- Raw escape hatches when needed: `session`, `events`, `lastEvent`, `lastAttemptEvent`, `verifiedEvent`, `lockedEvent`, `expiredEvent`
- `attempts`, `triesLeft`, `status`, `isWaiting`, `isVerified`, `isLocked`, `isExpired`

`useInboundCallOtp()` also exposes `otpCode`, `callHref`, and `qrCodeImage`.

`useInboundSmsOtp()` also exposes `decodedText` and `qrCodeImage`.

The `data` shape is channel-specific:

- Call hook data: `servicePhone`, `callerPhone`, `qrCodeImage`, `otpCode`, `callHref`
- SMS hook data: `servicePhone`, `senderPhone`, `qrCodeImage`, `rawText`, `decodedText`

## Options

```ts
type UseInboundCallOtpOptions = {
  autoWait?: boolean;
  waitMode?: 'websocket' | 'long-poll';
  timeoutMs?: number;
  baseUrl?: string;
  websocketBaseUrl?: string;
  client?: SiymoOtpClient;
  clientOptions?: Omit<SiymoOtpClientOptions, 'baseUrl' | 'websocketBaseUrl'>;
  onCreated?: (session, result) => void;
  onSubscribed?: (event, result) => void;
  onTried?: (event, result) => void;
  onSuccess?: (event, result) => void;
  onLocked?: (event, result) => void;
  onExpired?: (event, result) => void;
  onError?: (error, result) => void;
  onStatusChange?: (status, result) => void;
};
```

Notes:

- `waitMode` defaults to `websocket`
- `autoWait` defaults to `true`
- `baseUrl` still overrides any build-time default when you want a per-hook override
- Pass `qrCode: true` in `create()` when you want the API to return a scannable `qrCodeImage` data URL
- WebSocket mode is the only mode that can stream `otp.attempt` events live
- Long-poll mode still updates verified / locked / expired state, but it cannot stream intermediate attempts
- Callback handlers receive the latest hook result, including the derived `data` object

## Examples

The package includes two basic React examples:

- [`examples/inbound-call-client`](./examples/inbound-call-client)
- [`examples/inbound-sms-client`](./examples/inbound-sms-client)

Each example consumes this React package plus the core `@siymo/otp-sdk-core`.