{"_id":"@audin.ai/operator-sdk","_rev":"3-91e8a7d1c0b883e242de74d8e181354f","name":"@audin.ai/operator-sdk","dist-tags":{"latest":"0.5.0"},"versions":{"0.3.0":{"name":"@audin.ai/operator-sdk","version":"0.3.0","keywords":["audin","softphone","operator","websocket","voice","sdk"],"author":{"url":"https://audin.ai","name":"The Cove S.r.l."},"license":"MIT","_id":"@audin.ai/operator-sdk@0.3.0","maintainers":[{"name":"audin-hello","email":"hello@audin.ai"}],"homepage":"https://audin.ai","bugs":{"url":"https://audin.ai"},"dist":{"shasum":"1c943f0d6cded8475f5cd067ca8b2fc1f05c4ec2","tarball":"https://registry.npmjs.org/@audin.ai/operator-sdk/-/operator-sdk-0.3.0.tgz","fileCount":20,"integrity":"sha512-2H4n0Vzy2/8/f/LClvytb0p+zRiT6vfBYqyIeUL/xPojSQ6h/mKcJaXNY1t/RMBwUh4MFsc+hup0iMDCfybU8g==","signatures":[{"sig":"MEYCIQDrhkkNVaNY9xD9eKbHrOmI8f/MB3oWWfiqsVnSSv5svgIhALpmxxT5vlTY6JgoSTWMhQZEQox68KDySsvGQeF0dtIh","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":309480},"main":"./dist/audin-operator-sdk.umd.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/audin-operator-sdk.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/audin-operator-sdk.js","require":"./dist/audin-operator-sdk.umd.cjs"}},"gitHead":"039983182b4d4a3e6663d8e46d8a5c8ca2cc0048","scripts":{"dev":"vite","test":"vitest run","build":"tsc --emitDeclarationOnly --outDir dist && vite build","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"audin-hello","email":"hello@audin.ai"},"_npmVersion":"11.6.0","description":"Headless browser SDK for the Audin operator softphone — make and receive calls over the Audin operator WebSockets.","directories":{},"sideEffects":false,"_nodeVersion":"24.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.3.1","vitest":"^4.1.5","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/operator-sdk_0.3.0_1781086861954_0.7952555582917207","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@audin.ai/operator-sdk","version":"0.4.0","keywords":["audin","softphone","operator","websocket","voice","sdk"],"author":{"url":"https://audin.ai","name":"The Cove S.r.l."},"license":"MIT","_id":"@audin.ai/operator-sdk@0.4.0","maintainers":[{"name":"audin-hello","email":"hello@audin.ai"}],"homepage":"https://audin.ai","bugs":{"url":"https://audin.ai"},"dist":{"shasum":"aaa2513b55e24c921636a9406441e5467141bfb0","tarball":"https://registry.npmjs.org/@audin.ai/operator-sdk/-/operator-sdk-0.4.0.tgz","fileCount":20,"integrity":"sha512-8JTM2POZZBemySkQGSJCvaeDofuI4tUY0uIZbaIRDplfpvYzm6fdk8FXmbphe4Gwf79RIhw7EWf1p4J4vrDl1w==","signatures":[{"sig":"MEYCIQC6AonU5A3CKmUOINlkMJ3PFHUIfxsPI5JJmIokjqoH4AIhAPWKQ/nuTHu0QvOVDfcpRLi3HpDuTVbYXNgzvxR5+5b1","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":317325},"main":"./dist/audin-operator-sdk.umd.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/audin-operator-sdk.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/audin-operator-sdk.js","require":"./dist/audin-operator-sdk.umd.cjs"}},"gitHead":"10bb1a384582cf3c6a04cc967cf83dd920442472","scripts":{"dev":"vite","test":"vitest run","build":"tsc --emitDeclarationOnly --outDir dist && vite build","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"audin-hello","email":"hello@audin.ai"},"_npmVersion":"11.6.0","description":"Headless browser SDK for the Audin operator softphone — make and receive calls over the Audin operator WebSockets.","directories":{},"sideEffects":false,"_nodeVersion":"24.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vite":"^7.3.1","vitest":"^4.1.5","typescript":"^5.9.3"},"_npmOperationalInternal":{"tmp":"tmp/operator-sdk_0.4.0_1781109406533_0.26424886579143636","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@audin.ai/operator-sdk","version":"0.5.0","description":"Headless browser SDK for the Audin operator softphone — make and receive calls over the Audin operator WebSockets.","type":"module","main":"./dist/audin-operator-sdk.umd.cjs","module":"./dist/audin-operator-sdk.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/audin-operator-sdk.js","require":"./dist/audin-operator-sdk.umd.cjs"}},"publishConfig":{"access":"public"},"sideEffects":false,"scripts":{"dev":"vite","build":"tsc --emitDeclarationOnly --outDir dist && vite build","test":"vitest run","test:watch":"vitest","prepublishOnly":"npm run build"},"keywords":["audin","softphone","operator","websocket","voice","sdk"],"license":"MIT","author":{"name":"The Cove S.r.l.","url":"https://audin.ai"},"homepage":"https://audin.ai","bugs":{"url":"https://audin.ai"},"devDependencies":{"typescript":"^5.9.3","vite":"^7.3.1","vitest":"^4.1.5"},"gitHead":"42b01b93999430d41b0ce4233555a7183a5e9d94","_id":"@audin.ai/operator-sdk@0.5.0","_nodeVersion":"24.9.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-5hmg/uRCZduPWmGNtlMPv8m0hrXlDGdO1yPi73AOF221CBPBmwnBTi5qJnUTh7sBZdl2Uh9wpYLUPvy3o3eD+w==","shasum":"445b5a4b0983512cd67f0da8853defe0d27d0673","tarball":"https://registry.npmjs.org/@audin.ai/operator-sdk/-/operator-sdk-0.5.0.tgz","fileCount":20,"unpackedSize":326616,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIEels6xEEgWclbVBlfsK4ELxsFDYEVP0XSjAJMyYuUi4AiBz+herEUUn/OMzw0jErIy1gINveukEOi2gAnoWJheF8g=="}]},"_npmUser":{"name":"aleloca","email":"alessandro.locatelli95@gmail.com"},"directories":{},"maintainers":[{"name":"audin-hello","email":"hello@audin.ai"},{"name":"aleloca","email":"alessandro.locatelli95@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/operator-sdk_0.5.0_1781185074206_0.4983732458935273"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-10T10:21:01.785Z","modified":"2026-06-11T13:37:54.510Z","0.3.0":"2026-06-10T10:21:02.121Z","0.4.0":"2026-06-10T16:36:46.681Z","0.5.0":"2026-06-11T13:37:54.378Z"},"bugs":{"url":"https://audin.ai"},"author":{"name":"The Cove S.r.l.","url":"https://audin.ai"},"license":"MIT","homepage":"https://audin.ai","keywords":["audin","softphone","operator","websocket","voice","sdk"],"description":"Headless browser SDK for the Audin operator softphone — make and receive calls over the Audin operator WebSockets.","maintainers":[{"name":"audin-hello","email":"hello@audin.ai"},{"name":"aleloca","email":"alessandro.locatelli95@gmail.com"}],"readme":"# @audin.ai/operator-sdk\n\nHeadless browser SDK for the **Audin operator softphone**. Make and receive\nphone calls from a web app over the Audin operator WebSockets — the SDK handles\nthe microphone, audio transcoding, the signalling and media channels,\nreconnection and heartbeats. **There is no UI**: you build the interface, the\nSDK does the plumbing.\n\n- Framework-agnostic, zero runtime dependencies.\n- Ships as ESM and UMD with full TypeScript types.\n- Your account credentials **never enter the browser** — the SDK obtains\n  short-lived session tokens through a `getToken` callback you provide.\n\n---\n\n## Install\n\n```bash\nnpm install @audin.ai/operator-sdk\n```\n\nOr drop the UMD bundle in via a `<script>` tag (global `AudinOperatorSDK`):\n\n```html\n<script src=\"https://unpkg.com/@audin.ai/operator-sdk/dist/audin-operator-sdk.umd.cjs\"></script>\n<script>\n  const op = new AudinOperatorSDK.AudinOperator({ /* … */ });\n</script>\n```\n\n### Compatibility\n\nThe SDK communicates with the Audin operator service over a **versioned wire\nprotocol, currently `v1`**. The package follows [SemVer](https://semver.org):\npatch and minor releases keep that protocol compatible, while a **breaking\nchange to the wire protocol ships as a MAJOR version bump** (e.g. `1.x → 2.0`).\nPin to a compatible MAJOR range (`^0.1` while pre-1.0) and read the\n[CHANGELOG](./CHANGELOG.md) before upgrading across a MAJOR. Note that\npre-1.0, the public API may still evolve in minor releases.\n\n---\n\n## The token flow (read this first)\n\nThe SDK is given a callback, `getToken`, that returns a short-lived session\ntoken. **You implement `getToken` to call your own backend**, which holds your\nAudin Account API Key and proxies to the Audin API:\n\n```\nbrowser (SDK)  ──getToken()──▶  your backend  ──X-API-Key──▶  Audin API\n                                                              POST /operator-sessions/token\nbrowser (SDK)  ◀──{ token }───  your backend  ◀──{ token }──\n```\n\nYour backend endpoint (example):\n\n```ts\n// On YOUR server — the API key stays here, never in the browser.\napp.post(\"/api/operator/token\", async (req, res) => {\n  const r = await fetch(\"https://api.audin.ai/operator-sessions/token\", {\n    method: \"POST\",\n    headers: {\n      \"Content-Type\": \"application/json\",\n      \"X-API-Key\": process.env.AUDIN_ACCOUNT_API_KEY, // server-side secret\n    },\n    body: JSON.stringify({\n      operatorRef: req.user.id, // your stable operator identifier\n      displayName: req.user.name,\n      phoneNumberIds: req.user.assignedPhoneNumberIds,\n    }),\n  });\n  const data = await r.json();\n  // Return at least { token }. (expiresAt is optional but recommended.)\n  res.json({ token: data.token, expiresAt: data.expiresAt });\n});\n```\n\nThe token is valid for about an hour; the SDK calls `getToken` again whenever it\nneeds a fresh one (on connect, on reconnect, and when opening a call's audio\nleg), so always fetch a **new** token rather than caching an expired one.\n\n---\n\n## Quick start\n\n```ts\nimport { AudinOperator } from \"@audin.ai/operator-sdk\";\n\nconst op = new AudinOperator({\n  coreUrl: \"https://core.audin.ai\",\n  getToken: async () => {\n    const r = await fetch(\"/api/operator/token\", { method: \"POST\" });\n    return r.json(); // { token, expiresAt? }\n  },\n});\n\n// ── inbound ──────────────────────────────────────────────\nop.on(\"incomingCall\", (call) => {\n  console.log(\"ringing from\", call.from);\n  // Show your own \"accept / reject\" UI, then:\n  call.accept(); // or call.reject();\n});\n\nop.on(\"callStarted\", (call) => {\n  console.log(\"connected:\", call.callSid, call.direction);\n});\n\nop.on(\"callEnded\", (call) => {\n  console.log(\"ended:\", call.callSid, \"reason:\", call.endReason);\n});\n\nop.on(\"error\", (e) => console.error(e.code, e.message));\n\n// ── pick a number ────────────────────────────────────────\n// List the numbers this account owns (fetched via the SDK with the same\n// session token as the WebSockets — no API key in the browser):\nconst numbers = await op.listPhoneNumbers();\n// → [{ id: \"pn_1\", phoneNumber: \"+390299999999\", displayName: \"Milano\" }, ...]\nconst mine = numbers[0]; // e.g. the one the operator selected in your UI\n\n// Go online on the number's id.\nawait op.goOnline([mine.id]);\n\n// ── outbound ─────────────────────────────────────────────\n// Use the same number's E.164 as the caller ID.\nconst call = await op.dial(\"+39021234567\", { callerId: mine.phoneNumber });\ncall.mute(true);\ncall.mute(false);\ncall.sendDtmf(\"5\"); // see note: not yet forwarded to the phone network\ncall.hangup();\n\n// When the operator logs out / closes the app:\nawait op.goOffline();\n```\n\n---\n\n## API\n\n### `new AudinOperator(config)`\n\n| Option | Type | Default | Notes |\n|---|---|---|---|\n| `coreUrl` | `string` | — | Audin operator service base URL. `http(s)` is upgraded to `ws(s)` internally. |\n| `getToken` | `() => Promise<{ token: string; expiresAt?: string }>` | — | Fetches a fresh session token from **your** backend. |\n| `heartbeatIntervalMs` | `number` | `25000` | Presence keep-alive interval. |\n| `reconnectBackoffMs` | `number[]` | `[1000,2000,5000,10000,30000]` | Backoff schedule for presence reconnects. |\n| `audioConstraints` | `MediaTrackConstraints` | echo cancel + noise suppress + AGC | Passed to `getUserMedia({ audio })`. |\n| `logger` | `OperatorLogger` | `console` | Diagnostic sink. |\n\n### Methods\n\n- `listPhoneNumbers(): Promise<OperatorPhoneNumber[]>` — list the phone numbers\n  the account owns (`{ id, phoneNumber, displayName }`). Fetched via the SDK\n  using the same session token as the WebSockets (the Account API Key never\n  enters the browser). Use a number's `id` for `goOnline([...])` and its\n  `phoneNumber` (E.164) as the `callerId` for `dial`. On a persistent `401`\n  throws `OperatorRequestError` (`code: \"UNAUTHORIZED\"`); other failures throw\n  with `code: \"REQUEST_FAILED\"`.\n- `goOnline(phoneNumberIds: string[]): Promise<void>` — connect the presence\n  channel and announce availability. Call again to change the number set.\n- `goOffline(): Promise<void>` — drop availability, end any active call, close\n  the presence channel (stops auto-reconnect).\n- `dial(to: string, { callerId }): Promise<OperatorCall>` — place an outbound\n  call. Resolves when the platform accepts and the audio bridge is opening.\n- `get state: PresenceState` — `\"offline\" | \"connecting\" | \"online\" | \"reconnecting\"`.\n- `get currentCall: OperatorCall | null`.\n- `on / off / once(event, listener)` — typed event subscription; `on` returns\n  an unsubscribe function.\n\n### Events\n\n| Event | Payload | When |\n|---|---|---|\n| `presenceStateChanged` | `PresenceState` | presence channel state changes |\n| `availabilityChanged` | `{ accepted: string[]; rejected: string[] }` | server confirms which numbers you went online on |\n| `incomingCall` | `OperatorCall` | an inbound call is ringing |\n| `callStarted` | `OperatorCall` | audio is established (after accept / dial) |\n| `callEnded` | `OperatorCall` | a call terminated (inspect `endReason`) |\n| `error` | `{ code, message, cause? }` | a non-fatal error |\n\n### `OperatorCall`\n\n```ts\ninterface OperatorCall {\n  readonly callSid: string;\n  readonly direction: \"inbound\" | \"outbound\";\n  readonly from?: string;\n  readonly to?: string;\n  readonly state: \"ringing\" | \"connecting\" | \"active\" | \"ended\";\n  readonly endReason?: CallEndReason;\n  readonly muted: boolean;\n\n  accept(): void;        // answer an inbound offer (no-op unless ringing)\n  reject(): void;        // decline an inbound offer (no-op unless ringing)\n  mute(on: boolean): void;\n  sendDtmf(digit: string): void; // \"0\"-\"9\", \"*\", \"#\" — see note below\n  hangup(): void;\n}\n```\n\n> **`sendDtmf` is not yet supported end-to-end.** The digit is validated and\n> sent as a control message on the call channel, but the server does not yet\n> forward the tones onto the telephone network, so the remote party will not\n> hear them today — it is effectively a functional no-op for the far end. The\n> method (and its wire message) are kept so that enabling it server-side in a\n> future release needs no SDK change. Do not rely on it for IVR navigation yet.\n\n`endReason` is one of: `hangup`, `remote_hangup`, `taken_by_other`, `rejected`,\n`no_answer`, `failed`, `offline`.\n\n---\n\n## Concurrency (MVP)\n\nThis release handles **one active call at a time**. While a call is live:\n\n- an incoming offer is **automatically declined** (you won't get an\n  `incomingCall` event for it), and\n- `dial()` **rejects** with an error.\n\nThis keeps the audio graph and the state machine simple. Multi-line support can\nbe added later without changing this public API.\n\n---\n\n## How the audio works (for the curious)\n\nYou don't need to know any of this to use the SDK, but for completeness:\n\n- The microphone is captured with `getUserMedia` and fed into a Web Audio\n  `AudioContext`.\n- An `AudioWorklet` runs on the audio thread and does the format conversion:\n  on capture it downsamples from the context rate (typically 48 kHz) to 8 kHz\n  and encodes **G.711 μ-law**; on playback it decodes μ-law and upsamples back\n  to the context rate. Resampling is linear interpolation.\n- Encoded audio is streamed as binary frames over the call's audio WebSocket;\n  audio from the far end arrives the same way and is played back.\n- Mute, DTMF and hangup are small JSON control messages on the same socket.\n  (DTMF is sent but not yet forwarded to the phone network — see the\n  `sendDtmf` note above.)\n\nThe μ-law codec and the resampling helpers are also exported from the package\nroot (`encodeMuLaw`, `decodeMuLaw`, `resampleLinear`, …) for advanced\nintegrators who want to build their own audio path.\n\n---\n\n## Browser support\n\nRequires a modern browser with `AudioWorklet`, `getUserMedia` and `WebSocket`\n(all current Chromium, Firefox and Safari releases). The page must be served\nover **HTTPS** (or `localhost`) for microphone access. The first call may need a\nuser gesture to resume the `AudioContext` under autoplay policies.\n\n---\n\n## Manual-test demo\n\nA single static page for hands-on testing lives at\n[`examples/operator-demo.html`](./examples/operator-demo.html). It loads the\nUMD build and gives you a minimal operator console (go online, accept/reject\nincoming calls, dial, mute, hang up, event log).\n\n```bash\n# 1. Build so the UMD bundle exists (the page loads ../dist/audin-operator-sdk.umd.cjs)\nnpm run build\n\n# 2. Serve over http://localhost (microphone needs a secure context; file:// won't work)\nnpx serve .\n# then open http://localhost:3000/examples/operator-demo.html\n```\n\nThe demo needs a backend **you** control that exposes a token endpoint (see\n[The token flow](#the-token-flow-read-this-first)) — paste its URL into the\n\"token endpoint\" field. The Account API Key never enters the page. It is for\nmanual testing only, not a production UI.\n\n---\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n","readmeFilename":"README.md"}