---
title: "ہیڈلیس ہک"
description: "useThunderPhone React hook کے ساتھ مکمل طور پر حسبِ ضرورت وائس UI بنائیں"
---

`useThunderPhone` ہک آپ کو یوزر انٹرفیس پر مکمل کنٹرول دیتا ہے، جبکہ ThunderPhone وائس سیشن، آڈیو روٹنگ، اور کنکشن کی حالت کو منظم کرتا ہے۔ جب آپ مکمل طور پر حسبِ ضرورت UI چاہتے ہوں — اپنے بٹن، لے آؤٹس، اینیمیشنز، اور برانڈنگ — جبکہ ThunderPhone پسِ پردہ ہر چیز سنبھالے، تو اسے استعمال کریں۔

## ہیڈلیس ہک کب استعمال کریں

پہلے سے تیار `ThunderPhoneWidget` کمپوننٹ زیادہ تر استعمال کے معاملات کو پورا کرتا ہے، لیکن جب آپ کو درج ذیل کی ضرورت ہو تو ہیڈلیس ہک استعمال کریں:

- مکمل طور پر حسبِ ضرورت کال UI جو آپ کی ایپ کے ڈیزائن سسٹم سے مطابقت رکھتا ہو
- حقیقی وقت کے آڈیو لیولز سے چلنے والی آڈیو-ردعملی ویژولائزیشنز (ویوفارمز، آربز، دھڑکتے اشارے)
- حسبِ ضرورت کال فلو، جیسے کال سے پہلے کے فارمز، کال کے بعد کے سرویز، یا وائس کے ساتھ اِن لائن چیٹ
- کسی موجودہ کمپوننٹ لائبریری میں انضمام (Material UI، Chakra، Radix، وغیرہ)

---

## انسٹالیشن

```bash
npm install @thunderphone/widget
```

<Note>
  چونکہ آپ اپنا UI فراہم کر رہے ہیں، اس لیے ہیڈلیس ہک کے لیے `@thunderphone/widget/style.css` امپورٹ کرنا **ضروری نہیں** ہے۔ تاہم، آپ کو پھر بھی وہی `@thunderphone/widget` پیکیج انسٹال کرنا ہوگا۔
</Note>

---

## بنیادی استعمال

```tsx
import { useThunderPhone } from '@thunderphone/widget'

function CustomCallButton() {
  const phone = useThunderPhone({
    publishableKey: 'pk_live_your_publishable_key',
  })

  const handleClick = () => {
    if (phone.state === 'connected') {
      phone.disconnect()
    } else {
      phone.connect()
    }
  }

  return (
    <>
      <button onClick={handleClick} disabled={phone.state === 'connecting'}>
        {phone.state === 'connecting'
          ? 'Connecting...'
          : phone.state === 'connected'
            ? 'End call'
            : 'Start call'}
      </button>
      {phone.audio}
    </>
  )
}
```

<Warning>
  **آپ کو اپنے کمپوننٹ ٹری میں کہیں نہ کہیں `phone.audio` رینڈر کرنا لازمی ہے۔** یہ ایک غیر مرئی React ایلیمنٹ ہے جو بنیادی آڈیو کنکشن کو منظم کرتا ہے۔ اگر آپ اسے شامل نہیں کرتے، تو کوئی آڈیو نہیں چلے گی اور سیشن کام نہیں کرے گا۔
</Warning>

---

## اختیارات

ان اختیارات کو `UseThunderPhoneOptions` کے ذریعے `useThunderPhone` کو دیں:

| اختیار | قسم | ضروری | ڈیفالٹ | تفصیل |
|--------|------|----------|---------|-------------|
| `publishableKey` | `string` | ہاں | -- | پبلش ایبل API کلید (`pk_live_...`)۔ ایجنٹ کلید کی widget کنفیگریشن سے خودکار طور پر متعین ہوتا ہے۔ |
| `apiBase` | `string` | نہیں | `'https://api.thunderphone.com/v1'` | API بیس URL کا اووررائیڈ۔ |
| `language` | `string` | نہیں | -- | فی سیشن زبان کا اووررائیڈ — زبان کا کوڈ یا لوکیل، جیسے `en`، `es`، یا `fr-FR`۔ جب یہ متعین نہ ہو، تو ایجنٹ کی کنفیگر کردہ زبان لاگو ہوتی ہے۔ |
| `voice` | `string` | نہیں | -- | فی سیشن آواز کا اووررائیڈ — آواز کا نام، جیسے `maria`۔ جب یہ متعین نہ ہو، تو ایجنٹ کی کنفیگر کردہ آواز لاگو ہوتی ہے۔ |
| `context` | `string` | نہیں | -- | ایجنٹ کو دیا جانے والا فی سیشن حقائق پر مبنی صفحہ یا سائٹ کا سیاق۔ سرور کی طرف سے 12,000 حروف تک محدود کیا جاتا ہے۔ |
| `onConnect` | `() => void` | نہیں | -- | وائس سیشن کے کنیکٹ ہونے پر کال کیا جاتا ہے۔ |
| `onDisconnect` | `() => void` | نہیں | -- | سیشن ختم ہونے پر کال کیا جاتا ہے۔ |
| `onError` | `(error) => void` | نہیں | -- | خرابیوں پر کال کیا جاتا ہے۔ خرابی میں `error` (کوڈ) اور `message` فیلڈز ہوتے ہیں۔ |
| `ringtone` | `boolean \| string` | نہیں | `false` | کنیکٹ ہونے کے دوران رنگ ٹون چلائیں۔ ڈیفالٹ رنگ ٹون کے لیے `true`، یا حسبِ ضرورت آڈیو کے لیے URL اسٹرنگ استعمال کریں۔ |

<Note>
  ہک ہیڈلیس ہے: یہ `ThunderPhoneWidget` کے ظاہری پراپس (`theme`، `primaryColor`، `title`، `position`، `className`) قبول **نہیں** کرتا۔ انہیں پاس کرنا TypeScript کی خرابی ہے — پریزنٹیشن مکمل طور پر آپ کو بنانی ہے۔
</Note>

---

## واپسی کی قدر

ہک ایک `UseThunderPhoneReturn` آبجیکٹ واپس کرتا ہے:

| پراپرٹی | قسم | وضاحت |
|----------|------|-------------|
| `state` | `'idle' \| 'connecting' \| 'connected' \| 'disconnected' \| 'error'` | موجودہ کنکشن کی حالت۔ |
| `connect` | `() => void` | وائس سیشن شروع کریں۔ |
| `disconnect` | `() => void` | موجودہ سیشن ختم کریں۔ |
| `toggleMute` | `() => void` | مائیکروفون میوٹ کو آن یا آف کریں۔ |
| `isMuted` | `boolean` | آیا مائیکروفون اس وقت میوٹ ہے۔ |
| `error` | `string \| undefined` | جب حالت `'error'` ہو تو خرابی کا پیغام۔ |
| `agentName` | `string \| undefined` | منسلک ایجنٹ کا ڈسپلے نام۔ |
| `audioLevel` | `number` | **متروک -- ہمیشہ `0`۔** پسماندہ مطابقت کے لیے برقرار رکھا گیا ایک جامد پلیس ہولڈر؛ یہ کبھی اپ ڈیٹ نہیں ہوتا۔ اس کے بجائے `audioLevelRef.current` پڑھیں۔ |
| `audioLevelRef` | `React.RefObject<number>` | ایک قابلِ تبدیلی ref جس میں حقیقی وقت کا آڈیو لیول (0 سے 1) موجود ہوتا ہے -- ایجنٹ کی آواز اور وزیٹر کے مائیکروفون میں سے جو زیادہ بلند ہو -- جو React کے رینڈر سائیکل سے باہر، ہر اینیمیشن فریم پر اپ ڈیٹ ہوتا ہے۔ ہموار، جھٹکوں سے پاک اینیمیشنز کے لیے `requestAnimationFrame` لوپس کے اندر `audioLevelRef.current` پڑھیں، یا جب React state میں قدر درکار ہو تو اسے کسی وقفے پر سیمپل کریں۔ |
| `audio` | `ReactNode` | غیر مرئی عنصر جو آڈیو کنکشن سنبھالتا ہے -- **اسے رینڈر کرنا لازمی ہے**۔ |

---

## آڈیو پر ردعمل دینے والا UI

`audioLevelRef` ref آپ کو React کے دوبارہ رینڈر ہونے کو متحرک کیے بغیر فریم ریٹ آڈیو لیولز فراہم کرتا ہے، جس کی وجہ سے یہ ہموار ویوفارم ویژولائزیشنز، دھڑکتے ہوئے گولوں، یا گفتگو سے منسلک کسی بھی اینیمیشن کو چلانے کے لیے موزوں ہے۔ لیول اس آواز کی عکاسی کرتا ہے جو زیادہ بلند ہو: ایجنٹ کی آواز یا وزیٹر کا مائیکروفون۔

### ویوفارم کی مثال

```tsx
import { useRef, useEffect } from 'react'
import { useThunderPhone } from '@thunderphone/widget'

function WaveformCall() {
  const phone = useThunderPhone({
    publishableKey: 'pk_live_your_publishable_key',
  })
  const canvasRef = useRef<HTMLCanvasElement>(null)

  useEffect(() => {
    if (phone.state !== 'connected') return
    const canvas = canvasRef.current
    if (!canvas) return
    const ctx = canvas.getContext('2d')!

    let animId: number
    const draw = () => {
      const level = phone.audioLevelRef.current ?? 0
      ctx.clearRect(0, 0, canvas.width, canvas.height)

      // Draw bars that react to audio level
      const barCount = 24
      const barWidth = canvas.width / barCount
      for (let i = 0; i < barCount; i++) {
        const distance = Math.abs(i - barCount / 2) / (barCount / 2)
        const height = level * canvas.height * (1 - distance * 0.6)
        const y = (canvas.height - height) / 2
        ctx.fillStyle = '#0ea5e9'
        ctx.fillRect(i * barWidth + 1, y, barWidth - 2, height)
      }

      animId = requestAnimationFrame(draw)
    }
    animId = requestAnimationFrame(draw)
    return () => cancelAnimationFrame(animId)
  }, [phone.state, phone.audioLevelRef])

  return (
    <div>
      {phone.state === 'connected' && (
        <canvas ref={canvasRef} width={240} height={80} />
      )}
      <button
        onClick={phone.state === 'connected' ? phone.disconnect : phone.connect}
        disabled={phone.state === 'connecting'}
      >
        {phone.state === 'connected' ? 'End call' : 'Start call'}
      </button>
      {phone.audio}
    </div>
  )
}
```

### دھڑکتے ہوئے گولے کی مثال

```tsx
import { useRef, useEffect } from 'react'
import { useThunderPhone } from '@thunderphone/widget'

function PulsingOrb() {
  const phone = useThunderPhone({
    publishableKey: 'pk_live_your_publishable_key',
  })
  const orbRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    if (phone.state !== 'connected') return
    let animId: number
    const animate = () => {
      const level = phone.audioLevelRef.current ?? 0
      if (orbRef.current) {
        const scale = 1 + level * 0.5
        orbRef.current.style.transform = `scale(${scale})`
        orbRef.current.style.opacity = `${0.6 + level * 0.4}`
      }
      animId = requestAnimationFrame(animate)
    }
    animId = requestAnimationFrame(animate)
    return () => cancelAnimationFrame(animId)
  }, [phone.state, phone.audioLevelRef])

  return (
    <div style={{ textAlign: 'center' }}>
      <div
        ref={orbRef}
        style={{
          width: 80,
          height: 80,
          borderRadius: '50%',
          background: '#0ea5e9',
          margin: '20px auto',
          transition: 'transform 0.05s ease-out',
        }}
      />
      <button
        onClick={phone.state === 'connected' ? phone.disconnect : phone.connect}
        disabled={phone.state === 'connecting'}
      >
        {phone.state === 'connected' ? 'End call' : 'Call'}
      </button>
      {phone.audio}
    </div>
  )
}
```

### بولنے کے اشارے کی مثال

React سے رینڈر ہونے والے ایسے UI کے لیے جو آواز کی سطح کے ساتھ تبدیل ہوتا ہے -- مثلاً حد پر مبنی "بولنے" کا بیج -- وقفے سے `audioLevelRef.current` کا نمونہ لیں اور نتیجہ state میں محفوظ کریں:

```tsx
import { useEffect, useState } from 'react'
import { useThunderPhone } from '@thunderphone/widget'

function SpeakingBadge() {
  const phone = useThunderPhone({
    publishableKey: 'pk_live_your_publishable_key',
  })
  const [speaking, setSpeaking] = useState(false)

  useEffect(() => {
    if (phone.state !== 'connected') {
      setSpeaking(false)
      return
    }
    const interval = setInterval(() => {
      setSpeaking((phone.audioLevelRef.current ?? 0) > 0.1)
    }, 100)
    return () => clearInterval(interval)
  }, [phone.state, phone.audioLevelRef])

  return (
    <div>
      {phone.state === 'connected' && (
        <span>{speaking ? 'Speaking' : 'Listening'}</span>
      )}
      <button onClick={phone.state === 'connected' ? phone.disconnect : phone.connect}>
        {phone.state === 'connected' ? 'End call' : 'Start call'}
      </button>
      {phone.audio}
    </div>
  )
}
```

<Warning>
  ہمیشہ `audioLevelRef.current` سے لیولز پڑھیں۔ واپسی آبجیکٹ پر موجود `audioLevel` نمبر **متروک ہے اور ہمیشہ `0` رہتا ہے** -- اس پر مبنی کوئی بھی منطق خاموشی سے صفر پڑھے گی۔
</Warning>

---

## اسٹیٹ مشین

`state` پراپرٹی اس لائف سائیکل کی پیروی کرتی ہے:

```
idle --> connecting --> connected --> disconnected --> idle (after 1.5s)
                  \
                   --> error (stays until connect() is called again)
```

| اسٹیٹ | وضاحت |
|-------|-------------|
| `idle` | کوئی فعال سیشن نہیں۔ `connect()` کال کرنے کے لیے تیار ہے۔ |
| `connecting` | سیشن قائم کیا جا رہا ہے۔ اس اسٹیٹ کے دوران کال بٹن کو غیر فعال کریں۔ |
| `connected` | وائس سیشن فعال ہے۔ صارف ایجنٹ سے بات کر رہا ہے۔ |
| `disconnected` | سیشن صاف طور پر ختم ہو گیا ہے۔ 1.5 سیکنڈ بعد خودکار طور پر واپس `idle` میں منتقل ہو جاتا ہے۔ |
| `error` | کچھ غلط ہو گیا۔ پیغام کے لیے `phone.error` چیک کریں۔ اسٹیٹ خود سے صاف **نہیں** ہوتی -- دوبارہ `connect()` کال کرنے سے ایک نئی کوشش شروع ہوتی ہے اور خرابی ری سیٹ ہو جاتی ہے۔ |

---

## مثالیں

### میوٹ کنٹرول کے ساتھ

```tsx
import { useThunderPhone } from '@thunderphone/widget'

function CallWithMute() {
  const phone = useThunderPhone({
    publishableKey: 'pk_live_your_publishable_key',
  })

  return (
    <div>
      {phone.state === 'connected' && (
        <div>
          <p>Talking to {phone.agentName ?? 'Agent'}</p>
          <button onClick={phone.toggleMute}>
            {phone.isMuted ? 'Unmute' : 'Mute'}
          </button>
          <button onClick={phone.disconnect}>End call</button>
        </div>
      )}

      {phone.state !== 'connected' && (
        <button
          onClick={phone.connect}
          disabled={phone.state === 'connecting'}
        >
          {phone.state === 'connecting' ? 'Connecting...' : 'Call support'}
        </button>
      )}

      {phone.state === 'error' && (
        <p style={{ color: 'red' }}>{phone.error}</p>
      )}

      {phone.audio}
    </div>
  )
}
```

### رنگ ٹون کے ساتھ

فون کال کی نقل کے لیے کنیکٹ ہونے کے دوران رنگ ہونے کی آواز چلائیں:

```tsx
import { useThunderPhone } from '@thunderphone/widget'

function PhoneCallButton() {
  const phone = useThunderPhone({
    publishableKey: 'pk_live_your_publishable_key',
    ringtone: true, // or a custom URL: 'https://example.com/ringtone.mp3'
  })

  return (
    <>
      <button
        onClick={phone.state === 'connected' ? phone.disconnect : phone.connect}
        disabled={phone.state === 'connecting'}
      >
        {phone.state === 'connecting'
          ? 'Ringing...'
          : phone.state === 'connected'
            ? 'Hang up'
            : 'Call'}
      </button>
      {phone.audio}
    </>
  )
}
```

رنگ ٹون `connecting` حالت کے دوران مسلسل چلتی ہے اور ایجنٹ کے کنیکٹ ہونے پر آہستہ آہستہ ختم ہو جاتی ہے۔ بلٹ اِن ڈیفالٹ رنگ ٹون کے لیے `true` دیں، یا اپنی آڈیو فائل استعمال کرنے کے لیے URL اسٹرنگ دیں۔

### ایونٹ کال بیکس کے ساتھ

```tsx
import { useThunderPhone } from '@thunderphone/widget'

function TrackedCallButton() {
  const phone = useThunderPhone({
    publishableKey: 'pk_live_your_publishable_key',
    onConnect: () => {
      analytics.track('call_started')
    },
    onDisconnect: () => {
      analytics.track('call_ended')
    },
    onError: (error) => {
      analytics.track('call_error', { code: error.error, message: error.message })
    },
  })

  return (
    <>
      <button
        onClick={phone.state === 'connected' ? phone.disconnect : phone.connect}
        disabled={phone.state === 'connecting'}
      >
        {phone.state === 'connected' ? 'Hang up' : 'Talk to AI'}
      </button>
      {phone.audio}
    </>
  )
}
```

### مکمل کسٹم UI

```tsx
import { useThunderPhone } from '@thunderphone/widget'

function FullCustomUI() {
  const phone = useThunderPhone({
    publishableKey: 'pk_live_your_publishable_key',
  })

  return (
    <div className="call-panel">
      <div className="call-status">
        {phone.state === 'idle' && <span>Ready</span>}
        {phone.state === 'connecting' && <span className="pulse">Connecting...</span>}
        {phone.state === 'connected' && (
          <span>On call with {phone.agentName}</span>
        )}
        {phone.state === 'error' && <span className="error">{phone.error}</span>}
      </div>

      <div className="call-controls">
        {phone.state === 'connected' ? (
          <>
            <button className="mute-btn" onClick={phone.toggleMute}>
              {phone.isMuted ? 'Unmute' : 'Mute'}
            </button>
            <button className="end-btn" onClick={phone.disconnect}>
              End
            </button>
          </>
        ) : (
          <button
            className="start-btn"
            onClick={phone.connect}
            disabled={phone.state === 'connecting'}
          >
            Start call
          </button>
        )}
      </div>

      {/* Required -- handles audio under the hood */}
      {phone.audio}
    </div>
  )
}
```

---

## تجاویز

<AccordionGroup>
  <Accordion title="ہمیشہ phone.audio رینڈر کریں">
    `phone.audio` عنصر نظر نہیں آتا مگر ضروری ہے۔ اسے اپنے JSX میں کہیں بھی رکھیں -- یہ کوئی نظر آنے والا DOM رینڈر نہیں کرتا، لیکن اندرونی طور پر WebRTC آڈیو کنکشن کا انتظام کرتا ہے۔
  </Accordion>

  <Accordion title="کنیکٹ ہوتے وقت بٹن غیر فعال کریں">
    `connecting` حالت 1 سے 3 سیکنڈ تک رہ سکتی ہے۔ ڈپلیکیٹ کنکشن کوششوں سے بچنے کے لیے اس حالت کے دوران کال بٹن غیر فعال کریں۔
  </Accordion>

  <Accordion title="خرابی کی حالت کو مناسب طریقے سے سنبھالیں">
    جب حالت `error` ہو تو صارف کو `phone.error` دکھائیں اور اپنا کال بٹن فعال رکھیں۔ hook خود سے `error` حالت سے باہر نہیں آتا -- `connect()` کو دوبارہ کال کرنے سے ایک نئی کوشش شروع ہوتی ہے اور پچھلی خرابی صاف ہو جاتی ہے۔
  </Accordion>

  <Accordion title="ضمنی اثرات کے لیے callbacks استعمال کریں">
    `onConnect`، `onDisconnect`، اور `onError` callbacks اسٹیٹ کو poll کیے بغیر analytics، logging، یا دیگر ایپلیکیشن منطق کو trigger کرنے کے لیے موزوں ہیں۔
  </Accordion>

  <Accordion title="audioLevelRef سے آڈیو لیولز پڑھیں">
    `audioLevelRef` واحد لائیو آڈیو-لیول سورس ہے۔ waveform جیسی ہموار animations کے لیے `requestAnimationFrame` کے اندر `audioLevelRef.current` پڑھیں (ref پڑھنے سے دوبارہ رینڈر نہیں ہوتے)، یا اسے کسی interval پر sample کریں اور نتیجہ React سے رینڈر ہونے والے UI کے لیے state میں محفوظ کریں۔ `audioLevel` نمبر متروک ہے اور ہمیشہ `0` ہوتا ہے -- اس پر منطق نہ بنائیں۔
  </Accordion>
</AccordionGroup>
