@nexafon/react wraps the web SDK in typed React components. Supported: React 19.
npm install @nexafon/react1. Provide Nexafon once
Section titled “1. Provide Nexafon once”<NexafonProvider> takes the Nexafon.init options and starts Nexafon once, on mount; use one per page. Put it high
in your tree, inside your app’s signed-in area:
import { CallButton, Inbox, NexafonProvider, Softphone } from '@nexafon/react'
const getUserToken = async () => { const r = await fetch('/api/nexafon-token', { method: 'POST' }) if (!r.ok) throw new Error(`The token endpoint answered ${r.status}`) return r.text()}
export function App() { return ( <NexafonProvider publishableKey={import.meta.env.VITE_NEXAFON_PUBLISHABLE_KEY} getUserToken={getUserToken} > <main style={{ display: 'grid', gap: 16, padding: 16, gridTemplateColumns: '1fr 340px' }}> <Inbox style={{ height: 640 }} /> <aside> <CallButton phone="+252615550142" /> <Softphone /> </aside> </main> </NexafonProvider> )}2. Place components
Section titled “2. Place components”| Component | Renders | Props |
|---|---|---|
<NexafonApp/> |
<nexafon-app>, the whole dashboard |
store?, className, style, id |
<Inbox/> |
<nexafon-inbox> |
store?, className, style, id |
<Softphone/> |
<nexafon-softphone> |
store?, className, style, id |
<CallButton/> |
<nexafon-call-button> |
phone, store?, className, style, id |
3. Events and methods
Section titled “3. Events and methods”import { useNexafon, useNexafonEvent } from '@nexafon/react'
function OrderHeader({ phone }: { phone: string }) { const nexafon = useNexafon() // null on the server and until Nexafon has started useNexafonEvent('call.ringing', (call) => console.log('ringing', call.callerNumber)) return <button onClick={() => nexafon?.openChat(phone)}>Chat</button>}useNexafonEvent listens while the component is mounted; the events are listed in the
events reference.
The token endpoint
Section titled “The token endpoint”getUserToken calls your server, which signs a token with a server helper. The example’s
POST /api/nexafon-token handler:
import type { IncomingMessage, ServerResponse } from 'node:http'import { createUserToken } from '@nexafon/node'
interface SignedInUser { id: string name: string role: string email: string emailVerified: boolean isStaff: boolean}
// Your app's own session decides who is signed in, and its own records whether it has confirmed their email. The// example has no sign-in: EXAMPLE_DEMO_USER_EMAIL is a demo user for local runs only, so replace this with your session.function signedInUser(_req: IncomingMessage): SignedInUser | null { const email = process.env.EXAMPLE_DEMO_USER_EMAIL if (!email) return null return { id: 'demo-user-1', name: 'Demo User', role: process.env.EXAMPLE_DEMO_USER_ROLE || 'agent', email, // The demo address is a member you invited, so you have confirmed it. emailVerified: true, isStaff: true, }}
// Everyone who gets a token may reach your workspace's inbox and calls: sign only for your staff, never for customers// who signed up. The example's only user is the demo teammate.function mayUseNexafon(user: SignedInUser): boolean { return user.isStaff}
// POST /api/nexafon-token signs a token for the signed-in user with NEXAFON_SECRET_KEY.export function userToken(req: IncomingMessage, res: ServerResponse): void { if (req.method !== 'POST') { res.writeHead(405).end() return } const user = signedInUser(req) if (!user) { res.writeHead(401).end() return } if (!mayUseNexafon(user)) { res.writeHead(403).end() return } const token = createUserToken({ userId: user.id, name: user.name, role: user.role, email: user.email, // Nexafon links a first sign-in to the member with this email only when it is verified: never send true for an // address your app has not confirmed, or whoever sets a member's email on your app acts as that member. emailVerified: user.emailVerified, }) res.writeHead(200, { 'content-type': 'text/plain', 'cache-control': 'no-store' }).end(token)}signedInUser stands for your app’s own session: answer 401 when nobody is signed in. The example has no sign-in,
so on a local run it signs for the demo member named in EXAMPLE_DEMO_USER_EMAIL and answers 401 without one;
replace it with your session. mayUseNexafon answers 403 to signed-in users who must not use Nexafon: sign only for
your staff, never for customers who signed up, since a token can make its user a member of your workspace. The example
treats its demo user as staff.
The example mounts it as middleware of the Vite dev and preview servers in examples/react/vite.config.ts; in
production it is a route of your own server, behind your own session check.
The full example is examples/react (Vite), tested end to end with Playwright.