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

gatewire-react

v1.0.0

Published

React hooks and adapters for the Wiregate OTP API

Readme

gatewire-react

npm version CI License: MIT Coverage

React hooks and TypeScript types for integrating the Wiregate OTP verification flow into any React or Next.js application. Zero runtime dependencies.


Security note

Your Wiregate API key must never be embedded in client-side JavaScript.

gatewire-react never calls the Wiregate API directly. Instead, it accepts adapter functions that call your own backend proxy, which holds the real API key server-side. This is a hard architectural constraint — see Setting up your proxy for ready-to-use examples.

Browser (gatewire-react)
      │
      │  POST /api/otp/send  (no API key)
      ▼
Your Backend Proxy  ──── POST https://gatewire.raystate.com/api/v1/send-otp  (API key here)
                                       │
                                       ▼
                                 Wiregate API

Installation

npm install gatewire-react
# or
yarn add gatewire-react
# or
pnpm add gatewire-react

Peer dependency: React ≥ 17


Quick start

import { useOtp, createFetchAdapter } from 'gatewire-react';

// Point to your own backend proxy — never the Wiregate API directly
const adapter = createFetchAdapter('/api/otp');

function PhoneVerification() {
  const [phone, setPhone] = React.useState('');
  const [code, setCode] = React.useState('');

  const { state, error, sendOtp, verifyOtp, resend, resendCountdown } = useOtp({
    adapter,
    resendCooldown: 60,   // seconds before user can resend
    pollInterval: 3000,   // ms between status polls (0 to disable)
  });

  if (state === 'verified') return <p>Phone verified!</p>;

  if (state === 'idle' || state === 'sending') {
    return (
      <form onSubmit={e => { e.preventDefault(); sendOtp(phone); }}>
        <input
          type="tel"
          value={phone}
          onChange={e => setPhone(e.target.value)}
          placeholder="+213770123456"
        />
        <button type="submit" disabled={state === 'sending'}>
          {state === 'sending' ? 'Sending…' : 'Send code'}
        </button>
        {error && <p role="alert">{error}</p>}
      </form>
    );
  }

  return (
    <form onSubmit={e => { e.preventDefault(); verifyOtp(code); }}>
      <input
        type="text"
        value={code}
        onChange={e => setCode(e.target.value)}
        placeholder="6-digit code"
        maxLength={6}
      />
      <button type="submit" disabled={state === 'verifying'}>
        {state === 'verifying' ? 'Verifying…' : 'Verify'}
      </button>
      <button type="button" onClick={resend} disabled={resendCountdown > 0}>
        {resendCountdown > 0 ? `Resend in ${resendCountdown}s` : 'Resend code'}
      </button>
      {error && <p role="alert">{error}</p>}
    </form>
  );
}

How it works

useOtp({ adapter, pollInterval, resendCooldown })
  │
  ├── sendOtp(phone)      → calls adapter.sendOtp()  → POST /api/otp/send
  ├── verifyOtp(code)     → calls adapter.verifyOtp() → POST /api/otp/verify
  ├── resend()            → alias for sendOtp() with last phone (respects cooldown)
  ├── reset()             → resets all state
  └── (internal polling)  → calls adapter.getStatus() → GET /api/otp/status/:id

OTP state machine:

  idle
    │  sendOtp(phone)
    ▼
  sending  ──(error)──▶  idle (error set)
    │
    ▼
  pending  ──(poll)──▶  dispatched ──▶  sent
    │                                    │
    │                    ┌───────────────┤
    │                    ▼               ▼
    │                  failed         expired / cancelled
    │
    │  verifyOtp(code)
    ▼
  verifying ──(error)──▶ sent (error set, user can retry)
    │
    ▼
  verified  (terminal)

API reference

useOtp(options)

The main hook for the full OTP lifecycle.

function useOtp(options: UseOtpOptions): UseOtpReturn

UseOtpOptions

| Property | Type | Default | Description | |---|---|---|---| | adapter | GatewireAdapter | required | Object with sendOtp, verifyOtp, and optional getStatus functions | | pollInterval | number | 3000 | Status poll interval in ms. Set to 0 to disable polling | | resendCooldown | number | 60 | Seconds the user must wait before resending |

UseOtpReturn

| Property | Type | Description | |---|---|---| | state | OtpStatus | Current lifecycle state | | referenceId | string \| null | Set after a successful sendOtp call | | error | string \| null | Human-readable error from the last failed operation | | isLoading | boolean | true while sendOtp or verifyOtp is in flight | | resendCountdown | number | Seconds remaining in the resend cooldown | | sendOtp(phone, templateKey?) | () => Promise<void> | Send an OTP to the given phone number | | verifyOtp(code) | () => Promise<void> | Verify the code the user entered | | resend() | () => Promise<void> | Resend to the last phone (no-op if cooldown is active) | | reset() | () => void | Reset all state to initial values |

OtpStatus values: 'idle' | 'sending' | 'pending' | 'dispatched' | 'sent' | 'verifying' | 'verified' | 'failed' | 'expired' | 'cancelled'


useOtpStatus(referenceId, getStatus, options?)

Lower-level composable hook for polling the OTP status independently.

function useOtpStatus(
  referenceId: string | null,
  getStatus: GatewireAdapter['getStatus'],
  options?: { interval?: number; enabled?: boolean }
): { status: OtpStatus | null; error: string | null; isLoading: boolean }

Polls getStatus every interval ms (default 3000) while enabled !== false and referenceId is non-null. Automatically stops when a terminal status (sent, verified, failed, expired, cancelled) is received.


createFetchAdapter(proxyBaseUrl)

Creates a GatewireAdapter using the native fetch API that calls your backend proxy.

function createFetchAdapter(proxyBaseUrl: string): GatewireAdapter

Expected routes on your proxy:

| Method | Path | Forwards to | |---|---|---| | POST | {proxyBaseUrl}/send | Wiregate send-otp | | POST | {proxyBaseUrl}/verify | Wiregate verify-otp | | GET | {proxyBaseUrl}/status/:id | Wiregate status |

Example:

// All calls go to /api/otp/send, /api/otp/verify, /api/otp/status/:id
const adapter = createFetchAdapter('/api/otp');

GatewireError

Thrown by createFetchAdapter when the proxy returns a non-2xx response.

class GatewireError extends Error {
  readonly statusCode: number | undefined;
  readonly body: unknown;
}

Catch it to display user-friendly error messages:

import { GatewireError } from 'gatewire-react';

try {
  await adapter.sendOtp(phone);
} catch (err) {
  if (err instanceof GatewireError) {
    console.error(err.message, err.statusCode, err.body);
  }
}

Note: useOtp catches GatewireError automatically and sets the error field. Only non-GatewireError exceptions are re-thrown.


Custom adapter

You can write your own adapter instead of using createFetchAdapter:

import type { GatewireAdapter } from 'gatewire-react';

const adapter: GatewireAdapter = {
  async sendOtp(phone, templateKey) {
    const res = await myApiClient.post('/otp/send', { phone, template_key: templateKey });
    return res.data; // { reference_id, status: 'pending' }
  },
  async verifyOtp(referenceId, code) {
    const res = await myApiClient.post('/otp/verify', { reference_id: referenceId, code });
    return res.data; // { status: 'verified', message }
  },
  async getStatus(referenceId) {
    const res = await myApiClient.get(`/otp/status/${referenceId}`);
    return res.data; // { reference_id, status, created_at }
  },
};

Setting up your proxy

Your proxy server is the only place that holds the Wiregate API key. It receives requests from gatewire-react and forwards them to the Wiregate API.

Express / Node.js example

import express from 'express';
import { GateWireClient } from 'gatewire'; // Node.js SDK

const gw = new GateWireClient({ apiKey: process.env.GATEWIRE_API_KEY! });
const router = express.Router();

router.post('/otp/send', async (req, res) => {
  try {
    const { phone, template_key } = req.body as { phone: string; template_key?: string };
    const result = await gw.otp.send({ phone, templateKey: template_key });
    res.json(result);
  } catch (err) {
    const message = err instanceof Error ? err.message : 'Failed to send OTP';
    res.status(422).json({ error: message });
  }
});

router.post('/otp/verify', async (req, res) => {
  try {
    const { reference_id, code } = req.body as { reference_id: string; code: string };
    const result = await gw.otp.verify({ referenceId: reference_id, code });
    res.json(result);
  } catch (err) {
    const message = err instanceof Error ? err.message : 'Verification failed';
    res.status(400).json({ error: message });
  }
});

router.get('/otp/status/:id', async (req, res) => {
  try {
    const result = await gw.otp.getStatus(req.params.id!);
    res.json(result);
  } catch (err) {
    const message = err instanceof Error ? err.message : 'Status check failed';
    res.status(500).json({ error: message });
  }
});

export default router;

Next.js Route Handler example

// app/api/otp/send/route.ts
import { GateWireClient } from 'gatewire';

const gw = new GateWireClient({ apiKey: process.env.GATEWIRE_API_KEY! });

export async function POST(request: Request) {
  try {
    const { phone, template_key } = (await request.json()) as {
      phone: string;
      template_key?: string;
    };
    const result = await gw.otp.send({ phone, templateKey: template_key });
    return Response.json(result);
  } catch (err) {
    const message = err instanceof Error ? err.message : 'Failed to send OTP';
    return Response.json({ error: message }, { status: 422 });
  }
}
// app/api/otp/verify/route.ts
import { GateWireClient } from 'gatewire';

const gw = new GateWireClient({ apiKey: process.env.GATEWIRE_API_KEY! });

export async function POST(request: Request) {
  try {
    const { reference_id, code } = (await request.json()) as {
      reference_id: string;
      code: string;
    };
    const result = await gw.otp.verify({ referenceId: reference_id, code });
    return Response.json(result);
  } catch (err) {
    const message = err instanceof Error ? err.message : 'Verification failed';
    return Response.json({ error: message }, { status: 400 });
  }
}
// app/api/otp/status/[id]/route.ts
import { GateWireClient } from 'gatewire';

const gw = new GateWireClient({ apiKey: process.env.GATEWIRE_API_KEY! });

export async function GET(_request: Request, { params }: { params: { id: string } }) {
  try {
    const result = await gw.otp.getStatus(params.id);
    return Response.json(result);
  } catch (err) {
    const message = err instanceof Error ? err.message : 'Status check failed';
    return Response.json({ error: message }, { status: 500 });
  }
}

Then point the adapter at your proxy:

// In your React app — note: this is a relative URL, not the Wiregate API
const adapter = createFetchAdapter('/api/otp');

TypeScript

All types are exported from the package root:

import type {
  OtpStatus,
  GatewireAdapter,
  UseOtpOptions,
  UseOtpReturn,
  SendOtpResult,
  VerifyOtpResult,
  StatusResult,
} from 'gatewire-react';

The library is written in TypeScript with strict: true and ships declaration files (.d.ts) for full type safety.


Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feat/my-feature
  3. Install dependencies: npm install
  4. Make your changes, ensuring tests pass: npm test
  5. Check types: npm run typecheck
  6. Submit a pull request

Please follow the existing code style and add tests for any new behaviour.


License

MIT © GateWire Team