{"_id":"@bolna/web-call","name":"@bolna/web-call","dist-tags":{"latest":"3.0.1"},"versions":{"3.0.1":{"name":"@bolna/web-call","version":"3.0.1","description":"Bolna browser SDK for AI voice calls over WebRTC (SIP.js). Ephemeral per-call sessions — no API key in the browser.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/bolna-ai/web-call.git"},"type":"module","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"sideEffects":false,"publishConfig":{"access":"public"},"scripts":{"prepublishOnly":"npm run typecheck && npm test && npm run build","build":"tsup","typecheck":"tsc --noEmit","test":"vitest run","test:e2e":"playwright test"},"keywords":["bolna","voice","webrtc","sip","ai","call"],"dependencies":{"sip.js":"^0.21.2"},"devDependencies":{"@playwright/test":"^1.61.1","tsup":"^8.0.0","typescript":"^5.4.0","vitest":"^2.1.9"},"gitHead":"e3ad5207fce6f03b44ba74125142d42f457bea7f","_id":"@bolna/web-call@3.0.1","bugs":{"url":"https://github.com/bolna-ai/web-call/issues"},"homepage":"https://github.com/bolna-ai/web-call#readme","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-Ot5t8D1iaYBV0TzKAEtsUNFl2LbEsx76efsZZn/g4SfK9ronSE+64tT+J7g27xodMaaKMJCa5nQBWZLXU7b3fg==","shasum":"aa117c04276a2f2232690a322c4eeb5aafbe627e","tarball":"https://registry.npmjs.org/@bolna/web-call/-/web-call-3.0.1.tgz","fileCount":8,"unpackedSize":2869686,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCVd8Qa4ZAzWI1SkX9K76AJFcIDGIRZsGHdqJch9XGitgIgLd9nAU5oEv3aR4MM5tfe/omSENUwz2XwH7zMG4lLYR0="}]},"_npmUser":{"name":"bolna-ai","email":"pavan@bolna.dev"},"directories":{},"maintainers":[{"name":"prateeksachan","email":"prateek@bolna.dev"},{"name":"bolna-ai","email":"pavan@bolna.dev"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/web-call_3.0.1_1787211797832_0.23439380447660763"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-20T07:43:17.642Z","3.0.1":"2026-08-20T07:43:18.024Z","modified":"2026-08-20T07:43:18.260Z"},"maintainers":[{"name":"prateeksachan","email":"prateek@bolna.dev"},{"name":"bolna-ai","email":"pavan@bolna.dev"}],"description":"Bolna browser SDK for AI voice calls over WebRTC (SIP.js). Ephemeral per-call sessions — no API key in the browser.","homepage":"https://github.com/bolna-ai/web-call#readme","keywords":["bolna","voice","webrtc","sip","ai","call"],"repository":{"type":"git","url":"git+https://github.com/bolna-ai/web-call.git"},"bugs":{"url":"https://github.com/bolna-ai/web-call/issues"},"license":"MIT","readme":"# Bolna Web-Call SDK\n\n[![npm](https://img.shields.io/npm/v/@bolna/web-call)](https://www.npmjs.com/package/@bolna/web-call)\n\nAdd live AI voice calls to any web app. The SDK connects the browser to your Bolna agent\nover **WebRTC** (Opus audio, echo cancellation, TURN fallback) — one class, a few events.\n\n```js\nconst call = new BolnaWebCall({ sessionUrl: \"/api/bolna-session\" });\ncall.on(\"call-start\", () => console.log(\"agent connected\"));\ncall.on(\"call-end\", ({ reason }) => console.log(\"ended:\", reason));\nawait call.start();   // from a click handler\n```\n\n## Install\n\n**npm**\n\n```bash\nnpm install @bolna/web-call\n```\n\n```js\nimport { BolnaWebCall } from \"@bolna/web-call\";\n```\n\n**CDN / plain `<script>`**\n\n```html\n<script src=\"https://cdn.jsdelivr.net/gh/bolna-ai/web-call@v3.0.1/dist/bolna-web-call.min.js\"></script>\n<script>\n  const call = new BolnaWebCall({ sessionUrl: \"/api/bolna-session\" });\n</script>\n```\n\n## The security model (read this first)\n\nYour `bn-` API key must **never** ship in a web page — anyone could read it and place calls\non your account. Instead, the browser gets a **short-lived, single-use call session**:\n\n```\nbrowser ──POST /api/bolna-session──▶ YOUR backend ──bn- key──▶ Bolna mint\nbrowser ◀───────── ephemeral session (SIP creds ~120s TTL, TURN creds) ─────────┘\n```\n\nYour backend endpoint is ~10 lines — call Bolna's mint and return the JSON as-is:\n\n```js\n// e.g. Express — protect this route with YOUR user auth\napp.post(\"/api/bolna-session\", async (req, res) => {\n  const r = await fetch(\"https://api.bolna.ai/web-call/freeswitch-session\", {\n    method: \"POST\",\n    headers: { Authorization: `Bearer ${process.env.BOLNA_API_KEY}`, \"Content-Type\": \"application/json\" },\n    body: JSON.stringify({ agent_id: process.env.BOLNA_AGENT_ID }),\n  });\n  res.status(r.status).json(await r.json());\n});\n```\n\nA runnable version is in [`example/server-example.js`](example/server-example.js), and a full\ndemo page in [`example/sip-example.html`](example/sip-example.html).\n\nThe SDK deliberately has **no `apiKey` option**. The minted session expires in ~2 minutes and\nits SIP credential is consumed by the first call — leaking one is worth almost nothing.\n\n## API\n\n### `new BolnaWebCall(options)`\n\nProvide **exactly one** session source:\n\n| Option | Type | Use when |\n|---|---|---|\n| `sessionUrl` | `string` | You have a backend endpoint (POST, no body) returning the mint JSON — the standard setup |\n| `getSession` | `() => Promise<Session>` | You need custom fetch logic (auth headers, retries, framework client) |\n| `session` | `Session` | You already fetched a session this instant (it expires in ~120s) |\n\nOptional:\n\n| Option | Default | Purpose |\n|---|---|---|\n| `userData` | — | Dynamic variables for the call — substituted into the agent's prompt + welcome message (same as telephony `/call`'s `user_data`). Sent as `{\"user_data\": ...}` in the POST body to `sessionUrl`; forward it yourself if you use `getSession`/`session` |\n| `audio` | AEC/NS/AGC on | `MediaTrackConstraints` for the mic |\n| `iceTransportPolicy` | `\"all\"` | `\"relay\"` forces TURN (restrictive networks/testing) |\n| `audioElement` | hidden element | Play the agent through your own `<audio>` |\n| `debug` | `false` | Verbose logging |\n\n### Methods\n\n| Method | Description |\n|---|---|\n| `await call.start(opts?)` | Mint → mic permission → connect. Resolves when the agent answers. **Call from a user gesture** so audio is allowed to play. `opts.userData` overrides the constructor's `userData` for this call. |\n| `await call.stop()` | Hang up + release everything. Idempotent. |\n| `call.setMuted(bool)` / `call.isMuted()` | Toggle the mic without ending the call |\n| `call.getState()` | `\"idle\" \\| \"connecting\" \\| \"ringing\" \\| \"active\" \\| \"ended\"` |\n| `call.getRunId()` | The call's execution id (matches your call-history / webhooks) |\n\n### Events — `call.on(event, handler)` / `off` / `once`\n\n| Event | Payload | Fires when |\n|---|---|---|\n| `state-change` | `CallState` | Any state transition |\n| `media-permission` | — | Mic permission granted |\n| `call-start` | — | Agent answered, audio flowing |\n| `call-end` | `{ reason }` | `\"local-hangup\"`, `\"remote-hangup\"`, or `\"failed\"` |\n| `error` | `{ code, message, scope?, cause? }` | See error table |\n| `volume-level` | `0..1` | Agent audio level, ~10×/sec (drive a meter/avatar) |\n\n### Error codes\n\n| `code` | Meaning | Typical handling |\n|---|---|---|\n| `mint_failed` | Your session endpoint failed | Check your backend / network |\n| `at_capacity` | Concurrent-call limit hit (`scope`: `global`/`customer`) | Show \"all lines busy, retry shortly\" |\n| `microphone_denied` | User blocked the mic | Show mic-permission help |\n| `connect_failed` | Network/server unreachable or setup timeout | Retry with a fresh `start()` |\n| `call_rejected` | Server declined the call | Check agent id / session freshness |\n| `autoplay_blocked` | Browser blocked audio playback | Call `start()` from a click handler |\n| `already_active` | `start()` while a call is live | One call per instance at a time |\n\n## Behavior notes\n\n- **One call at a time** per instance — a second `start()` rejects instead of double-dialing.\n- **Sessions are fetched per call**, inside `start()`, so the short credential TTL can't lapse.\n  Nothing is ever written to `localStorage`.\n- **Echo cancellation** is on by default (recommended: leave it on; headphones for best results).\n- **Tab close / navigation** hangs the call up automatically (`pagehide`), so abandoned calls\n  release capacity immediately.\n- All media is **DTLS-SRTP encrypted**; TURN relay credentials are per-call and time-boxed.\n\n## Legacy library (v1)\n\n`bolna-webcall-library.js` (the v1.0.x direct-WebSocket library) remains in this repo unchanged\n— existing jsDelivr pins keep working. New integrations should use the v3 SDK above: better\naudio (Opus + jitter buffer vs raw 16k PCM), standard WebRTC, and no key-handling foot-guns.\n\n## Development\n\n```bash\nnpm install\nnpm run build      # dist/index.js (+ d.ts) and dist/bolna-web-call.min.js\nnpm run typecheck\nnpm test           # unit tests (vitest)\nnpm run test:e2e   # browser tests (playwright)\n```\n\nDemo: `BOLNA_API_KEY=bn-... BOLNA_AGENT_ID=... node example/server-example.js`, serve the repo\n(`python3 -m http.server 8081`), open `http://localhost:8081/example/sip-example.html`.\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","readmeFilename":"README.md","_rev":"1-d11a823a54d96b87d774de29f8717eda"}