@talkif/webrtc 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # Talkif WebRTC JS
2
+
3
+ Browser client libraries for talking to [Talkif](https://talkif.ai) AI voice agents over WebRTC.
4
+
5
+ | Package | Description |
6
+ |---|---|
7
+ | [`@talkif/webrtc`](packages/core) | Framework-agnostic core: signaling, peer connection, audio, data-channel protocol, realtime events |
8
+ | [`@talkif/webrtc-react`](packages/react) | Headless React hook (`useTalkifCall`) on top of the core |
9
+
10
+ Both packages are headless — no UI ships with them. Requires a browser with WebRTC and microphone access; the React package needs React ≥ 18.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @talkif/webrtc
16
+ # React bindings (peer-depends on @talkif/webrtc)
17
+ npm install @talkif/webrtc-react
18
+ ```
19
+
20
+ ## Two modes
21
+
22
+ The libraries speak to the Talkif API in one of two modes, chosen by the config shape you pass:
23
+
24
+ **Authenticated mode** — for your own dashboard or backend-authenticated apps. You supply the account id and an `auth` provider returning the `Authorization` header value. Call any published flow, or test a draft flow definition.
25
+
26
+ ```ts
27
+ const config = {
28
+ baseUrl: 'https://api.talkif.ai',
29
+ accountId: '<account-id>',
30
+ auth: async () => `Bearer ${await getToken()}`,
31
+ };
32
+ ```
33
+
34
+ **Public (embed) mode** — for widgets on any web page. You ship only a publishable key (`pk_live_...`, safe in page source); the library exchanges it for a short-lived session token itself. The page's origin must be on the key's allowlist, the flow is bound to the key, and realtime call events (transcripts, word timing, speaking indicators) stream over an events WebSocket.
35
+
36
+ ```ts
37
+ const config = {
38
+ baseUrl: 'https://api.talkif.ai',
39
+ publishableKey: 'pk_live_...',
40
+ };
41
+ ```
42
+
43
+ If the flow's bot gate is active, the library handles the Cloudflare Turnstile challenge invisibly — no setup needed. To supply your own token instead, pass `turnstileToken: () => Promise<string>`.
44
+
45
+ ## Quick start (core)
46
+
47
+ ```ts
48
+ import { TalkifCall } from '@talkif/webrtc';
49
+
50
+ const call = new TalkifCall(config);
51
+
52
+ call.on('connected', ({ callId }) => console.log('live', callId));
53
+ call.on('transcript', ({ role, content }) => console.log(role, content));
54
+ call.on('ended', ({ reason }) => console.log('ended', reason));
55
+ call.on('error', ({ error }) => console.error(error.code, error.message));
56
+
57
+ await call.start({ flowId: '<published-flow-id>' }); // flowId ignored in public mode
58
+
59
+ // later
60
+ call.setMuted(true);
61
+ call.hangup();
62
+ ```
63
+
64
+ ### `TalkifCall` API
65
+
66
+ | Member | Description |
67
+ |---|---|
68
+ | `start(options)` | Request mic, create the call, connect. `options`: `flowId`, `draftDefinition` (authenticated test calls), `metadata`, `audio` (getUserMedia constraints), `audioElement` (your own `<audio>`, or `null` to handle the `track` event yourself) |
69
+ | `hangup()` | End the call locally. Always safe to call. |
70
+ | `dispose()` | Tear everything down (mic, peer connection, timers, listeners). Instances are single-use. |
71
+ | `setMuted(muted)` | Toggle the local microphone track. |
72
+ | `sendAppMessage(json)` | Send an arbitrary JSON message to the bot over the data channel. Returns `false` if the channel isn't open. |
73
+ | `on(event, fn)` / `off(event, fn)` | Subscribe / unsubscribe. `on` returns an unsubscribe function. |
74
+ | `state`, `callId`, `botId`, `muted`, `durationSecs` | Current snapshot getters. |
75
+
76
+ State machine: `idle → requesting → connecting → connected → ended` (or `error`).
77
+
78
+ ### Events
79
+
80
+ | Event | Payload | When |
81
+ |---|---|---|
82
+ | `statechange` | `{ state, previous }` | Every state transition |
83
+ | `connected` | `{ callId, botId }` | Media flowing, answer applied |
84
+ | `track` | `{ stream }` | Remote (bot) audio track arrived |
85
+ | `tick` | `{ durationSecs }` | Once per second while connected |
86
+ | `mutechange` | `{ muted }` | Mute toggled |
87
+ | `appmessage` | `{ message }` | JSON from the bot over the data channel |
88
+ | `renegotiated` | `{ ok }` | Bot-initiated renegotiation attempted |
89
+ | `ended` | `{ reason }` | `local-hangup` \| `peer-left` \| `terminal-status` \| `external` |
90
+ | `error` | `{ error: TalkifCallError }` | Fatal failure; call is torn down |
91
+
92
+ **Realtime call events** (public mode — delivered over the events WebSocket):
93
+
94
+ | Event | Payload | Notes |
95
+ |---|---|---|
96
+ | `transcript` | `{ role, content, data }` | Final transcript line for a turn. On interruption, `content` includes text synthesized but never spoken. |
97
+ | `interim` | `{ data }` | Caller's in-progress transcription. Droppable under backpressure. |
98
+ | `ttschunk` | `{ text, timestamp, data }` | Sentence-level chunk of the agent's reply, emitted when the LLM output reaches synthesis — ahead of audio playback. Droppable. |
99
+ | `ttsword` | `{ word, ptsMs, timestamp, data }` | Word-level timing at playback pace (karaoke-style captions). Only words actually spoken. Droppable. |
100
+ | `callevent` | `{ event }` | Every delivered envelope, including types without a dedicated event (`speech`, `turn`, `node_transition`, `status`, `error`). |
101
+
102
+ "Droppable" events may be shed by the server under backpressure; the final `transcript` is authoritative — reconcile incremental displays against it.
103
+
104
+ ### Errors
105
+
106
+ `error.code` on `TalkifCallError`:
107
+
108
+ | Code | Meaning |
109
+ |---|---|
110
+ | `media-unavailable` | Microphone denied or unavailable |
111
+ | `signaling-failed` | Call creation / SDP exchange failed |
112
+ | `connection-failed` | ICE/peer connection failed |
113
+ | `pipeline-dead` | Connected, but the bot pipeline never became healthy |
114
+ | `renegotiation-failed` | Bot-requested renegotiation failed |
115
+ | `session-rejected` | Publishable key rejected, origin not allowed, or session expired |
116
+ | `call-active` | This session already has an active call (another tab) |
117
+ | `rate-limited` | Rate/concurrency limit hit — retry later |
118
+ | `no-agent` | No agent available right now — retry shortly |
119
+
120
+ ## Quick start (React)
121
+
122
+ ```tsx
123
+ import { useTalkifCall } from '@talkif/webrtc-react';
124
+
125
+ function CallWidget({ config }) {
126
+ const { state, durationSecs, muted, start, hangup, toggleMute } = useTalkifCall({
127
+ config,
128
+ onTranscript: ({ role, content }) => console.log(role, content),
129
+ onTtsChunk: ({ text }) => console.log('agent (streaming):', text),
130
+ onEnded: (reason) => console.log('ended', reason),
131
+ onError: (error) => console.error(error.code),
132
+ });
133
+
134
+ return state === 'connected' ? (
135
+ <>
136
+ <span>{durationSecs}s</span>
137
+ <button onClick={toggleMute}>{muted ? 'Unmute' : 'Mute'}</button>
138
+ <button onClick={hangup}>Hang up</button>
139
+ </>
140
+ ) : (
141
+ <button onClick={() => start({})} disabled={state === 'requesting' || state === 'connecting'}>
142
+ Start call
143
+ </button>
144
+ );
145
+ }
146
+ ```
147
+
148
+ `useTalkifCall` returns `{ state, callId, botId, error, durationSecs, muted, call, start, hangup, toggleMute, sendAppMessage }`. Callbacks: `onAppMessage`, `onCallEvent`, `onTranscript`, `onTtsWord`, `onTtsChunk`, `onEnded`, `onError`. One active call at a time — starting a new call disposes the previous one, and unmount tears everything down (mic, peer connection, timers).
149
+
150
+ ## What the core handles for you
151
+
152
+ - **Signaling** against the Talkif API: call creation (published or draft flows), TURN credential fetch, SDP offer/answer relay, public session token exchange and refresh.
153
+ - **Fast connect**: relay-only ICE with first-relay-candidate dispatch (no 5s+ gathering stalls), bot warmup runs in parallel with local media setup.
154
+ - **Data-channel protocol** spoken by Talkif bots: keepalive pings, `peerLeft` teardown, bot-initiated renegotiation, arbitrary JSON app messages both ways.
155
+ - **Realtime events** (public mode): WebSocket with auto-reconnect, backoff, and server-side replay after a gap — a dropped connection never loses call history.
156
+ - **Health watchdog**: if no health signal arrives after connect, call liveness is verified over REST before anything is torn down — a silent event stream never kills a healthy call.
157
+ - **Deterministic teardown**: microphone, audio element, timers, and the peer connection are always released on hangup/error/dispose.
158
+
159
+ ## What it deliberately does not do
160
+
161
+ - No UI. Both packages are headless; bring your own components.
162
+ - No transcript/state store. Subscribe to events and feed whatever store you use.
163
+ - No auth storage. You supply an `auth` provider (or a publishable key); the library never persists credentials.
164
+
165
+ ## Development
166
+
167
+ ```bash
168
+ pnpm install
169
+ pnpm build
170
+ pnpm test
171
+ pnpm type-check
172
+ ```
173
+
174
+ Requires Node ≥ 20 and pnpm ≥ 10.
175
+
176
+ ## License
177
+
178
+ [MIT](LICENSE)
package/dist/index.cjs CHANGED
@@ -132,7 +132,6 @@ var CallEventsClient = class {
132
132
  ws = null;
133
133
  closed = false;
134
134
  attempts = 0;
135
- everConnected = false;
136
135
  lastSeq = 0;
137
136
  reconnectTimer = null;
138
137
  constructor(options) {
@@ -169,9 +168,7 @@ var CallEventsClient = class {
169
168
  this.ws = ws;
170
169
  ws.onopen = () => {
171
170
  ws.send(JSON.stringify({ type: "auth", token }));
172
- ws.send(
173
- JSON.stringify({ type: "subscribe", ...this.everConnected ? { replay: true } : {} })
174
- );
171
+ ws.send(JSON.stringify({ type: "subscribe", replay: true }));
175
172
  };
176
173
  ws.onmessage = (message) => {
177
174
  if (typeof message.data !== "string") return;
@@ -183,7 +180,6 @@ var CallEventsClient = class {
183
180
  }
184
181
  if (typeof envelope.type !== "string" || typeof envelope.seq !== "number") return;
185
182
  if (envelope.type === "subscribed") {
186
- this.everConnected = true;
187
183
  this.attempts = 0;
188
184
  this.lastSeq = envelope.seq;
189
185
  return;
@@ -647,6 +643,7 @@ var TalkifCall = class {
647
643
  this.pc = local.pc;
648
644
  this.localStream = local.stream;
649
645
  this.setState("connecting");
646
+ this.connectEvents();
650
647
  this.pc.ontrack = (event) => {
651
648
  const stream = event.streams[0];
652
649
  if (!stream) return;
@@ -673,7 +670,6 @@ var TalkifCall = class {
673
670
  this.emitter.emit("connected", { callId: callResponse.callId, botId: this._botId });
674
671
  this.startDurationTimer();
675
672
  this.armHealthWatchdog(callResponse.callId);
676
- this.connectEvents();
677
673
  } catch (error) {
678
674
  this.fail(
679
675
  error instanceof TalkifCallError ? error : asSignalingCallError(error)
@@ -829,7 +825,8 @@ var TalkifCall = class {
829
825
  case "tts_word": {
830
826
  const word = typeof event.data.word === "string" ? event.data.word : "";
831
827
  if (!word) break;
832
- const ptsMs = typeof event.data.pts_ms === "number" ? event.data.pts_ms : null;
828
+ const ptsRaw = event.data.ptsMs ?? event.data.pts_ms;
829
+ const ptsMs = typeof ptsRaw === "number" ? ptsRaw : null;
833
830
  const timestamp = typeof event.data.timestamp === "number" ? event.data.timestamp : 0;
834
831
  this.emitter.emit("ttsword", { word, ptsMs, timestamp, data: event.data });
835
832
  break;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/data-channel.ts","../src/emitter.ts","../src/events.ts","../src/types.ts","../src/turnstile.ts","../src/signaling.ts","../src/call.ts"],"sourcesContent":["export { TalkifCall } from './call.js';\nexport { CallEventsClient } from './events.js';\nexport { PublicSession, SignalingClient, SignalingError } from './signaling.js';\nexport { TurnstileManager } from './turnstile.js';\nexport { isPublicConfig, TalkifCallError, TERMINAL_CALL_STATUSES } from './types.js';\nexport type {\n\tAppMessage,\n\tAuthProvider,\n\tCallEndReason,\n\tCallEventEnvelope,\n\tCallEventType,\n\tCallState,\n\tCallStatus,\n\tCreateCallRequest,\n\tCreateCallResponse,\n\tCreateTestCallRequest,\n\tIceServer,\n\tIceServersResponse,\n\tOfferRequest,\n\tOfferResponse,\n\tPeerLeftMessage,\n\tPublicSessionResponse,\n\tRenegotiateMessage,\n\tSignallingEnvelope,\n\tSignallingPayload,\n\tStartCallOptions,\n\tTalkifCallErrorCode,\n\tTalkifCallEventMap,\n\tTalkifClientConfig,\n\tTalkifConfig,\n\tTalkifPublicClientConfig,\n\tTrackStatusMessage,\n\tTtsChunkEvent,\n\tTtsWordEvent,\n\tWebRTCCall,\n} from './types.js';\n","import type { AppMessage, SignallingPayload, TrackStatusMessage } from './types.js';\n\nconst KEEPALIVE_INTERVAL_MS = 2_000;\n\nexport interface DataChannelHandlers {\n\tonSignalling: (message: SignallingPayload) => void;\n\tonAppMessage: (message: AppMessage) => void;\n}\n\n/**\n * Client side of the pipecat SmallWebRTC data-channel protocol:\n *\n * - `\"ping\"` strings every 2s keep the bot's liveness check happy (the bot\n * treats a connection as active when the last ping is <3s old, with the\n * ICE connection state as fallback).\n * - Inbound JSON is either `{type: \"signalling\", message: {...}}` (protocol)\n * or an arbitrary app message (forwarded to the consumer).\n */\nexport class DataChannel {\n\tprivate channel: RTCDataChannel;\n\tprivate keepalive: ReturnType<typeof setInterval> | null = null;\n\tprivate readonly handlers: DataChannelHandlers;\n\n\tconstructor(pc: RTCPeerConnection, handlers: DataChannelHandlers) {\n\t\tthis.handlers = handlers;\n\t\tthis.channel = pc.createDataChannel('pipecat', { ordered: true });\n\t\tthis.channel.onopen = () => this.startKeepalive();\n\t\tthis.channel.onclose = () => this.stopKeepalive();\n\t\tthis.channel.onmessage = (event) => this.handleMessage(event.data);\n\t}\n\n\tprivate startKeepalive(): void {\n\t\tthis.stopKeepalive();\n\t\tthis.keepalive = setInterval(() => {\n\t\t\tif (this.channel.readyState === 'open') {\n\t\t\t\tthis.channel.send('ping');\n\t\t\t}\n\t\t}, KEEPALIVE_INTERVAL_MS);\n\t}\n\n\tprivate stopKeepalive(): void {\n\t\tif (this.keepalive) {\n\t\t\tclearInterval(this.keepalive);\n\t\t\tthis.keepalive = null;\n\t\t}\n\t}\n\n\tprivate handleMessage(data: unknown): void {\n\t\tif (typeof data !== 'string') return;\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(data);\n\t\t} catch {\n\t\t\treturn; // Non-JSON payloads (e.g. bot-side pings) carry no protocol meaning here.\n\t\t}\n\t\tif (typeof parsed !== 'object' || parsed === null) return;\n\n\t\tconst record = parsed as Record<string, unknown>;\n\t\tif (record['type'] === 'signalling' && typeof record['message'] === 'object' && record['message'] !== null) {\n\t\t\tthis.handlers.onSignalling(record['message'] as SignallingPayload);\n\t\t\treturn;\n\t\t}\n\t\tthis.handlers.onAppMessage(record as AppMessage);\n\t}\n\n\t/** Send an arbitrary JSON app message to the bot pipeline. */\n\tsendAppMessage(message: AppMessage): boolean {\n\t\tif (this.channel.readyState !== 'open') return false;\n\t\tthis.channel.send(JSON.stringify(message));\n\t\treturn true;\n\t}\n\n\t/** Tell the bot to enable/disable one of our media receivers. */\n\tsendTrackStatus(message: Omit<TrackStatusMessage, 'type'>): boolean {\n\t\tif (this.channel.readyState !== 'open') return false;\n\t\tthis.channel.send(JSON.stringify({ type: 'signalling', message: { type: 'trackStatus', ...message } }));\n\t\treturn true;\n\t}\n\n\tclose(): void {\n\t\tthis.stopKeepalive();\n\t\tthis.channel.onopen = null;\n\t\tthis.channel.onclose = null;\n\t\tthis.channel.onmessage = null;\n\t\tif (this.channel.readyState === 'open' || this.channel.readyState === 'connecting') {\n\t\t\tthis.channel.close();\n\t\t}\n\t}\n}\n","/** Minimal typed event emitter — no dependency, browser-safe. */\nexport class Emitter<Events extends object> {\n\tprivate listeners = new Map<keyof Events, Set<(payload: never) => void>>();\n\n\ton<K extends keyof Events>(event: K, listener: (payload: Events[K]) => void): () => void {\n\t\tlet set = this.listeners.get(event);\n\t\tif (!set) {\n\t\t\tset = new Set();\n\t\t\tthis.listeners.set(event, set);\n\t\t}\n\t\tset.add(listener as (payload: never) => void);\n\t\treturn () => this.off(event, listener);\n\t}\n\n\toff<K extends keyof Events>(event: K, listener: (payload: Events[K]) => void): void {\n\t\tthis.listeners.get(event)?.delete(listener as (payload: never) => void);\n\t}\n\n\temit<K extends keyof Events>(event: K, payload: Events[K]): void {\n\t\tconst set = this.listeners.get(event);\n\t\tif (!set) return;\n\t\tfor (const listener of [...set]) {\n\t\t\t(listener as (payload: Events[K]) => void)(payload);\n\t\t}\n\t}\n\n\tremoveAll(): void {\n\t\tthis.listeners.clear();\n\t}\n}\n","import type { CallEventEnvelope } from './types.js';\n\n/** Reconnect backoff schedule (ms). Caps at the last entry. */\nconst BACKOFF_MS = [500, 1_000, 2_000, 4_000, 8_000];\n\nexport interface CallEventsOptions {\n\t/** API origin, e.g. `https://api.talkif.ai`. */\n\tbaseUrl: string;\n\t/** Fresh session token per (re)connect — first-message auth. */\n\tgetToken: () => Promise<string>;\n\t/** Every delivered envelope for the session's call. */\n\tonEvent: (event: CallEventEnvelope) => void;\n\t/**\n\t * A `seq` gap was observed: this connection shed frames under backpressure.\n\t * The client auto-requests a server-side replay on reconnect; consumers that\n\t * render transcripts incrementally may want to clear-and-rebuild on replay.\n\t */\n\tonGap?: (expected: number, received: number) => void;\n\t/** Optional WebSocket constructor override (tests, Node). */\n\twebSocket?: typeof WebSocket;\n}\n\n/**\n * Client for the public realtime events WebSocket\n * (`/api/v1/public/ws/events`).\n *\n * Protocol: connect → send `{\"type\":\"auth\",\"token\":...}` within 5s → send\n * `{\"type\":\"subscribe\"}` (the session is auto-bound to its one call) →\n * receive `{v, type, call_id?, seq, ts, data}` envelopes. Reconnects with\n * backoff; after the first successful connection, reconnect subscribes with\n * `replay: true` to recover anything missed during the gap.\n */\nexport class CallEventsClient {\n\tprivate readonly options: CallEventsOptions;\n\tprivate ws: WebSocket | null = null;\n\tprivate closed = false;\n\tprivate attempts = 0;\n\tprivate everConnected = false;\n\tprivate lastSeq = 0;\n\tprivate reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n\tconstructor(options: CallEventsOptions) {\n\t\tthis.options = options;\n\t}\n\n\tconnect(): void {\n\t\tif (this.closed || this.ws) return;\n\t\tvoid this.open();\n\t}\n\n\tclose(): void {\n\t\tthis.closed = true;\n\t\tif (this.reconnectTimer) {\n\t\t\tclearTimeout(this.reconnectTimer);\n\t\t\tthis.reconnectTimer = null;\n\t\t}\n\t\tthis.ws?.close(1000);\n\t\tthis.ws = null;\n\t}\n\n\tprivate wsUrl(): string {\n\t\tconst base = this.options.baseUrl.replace(/\\/$/, '').replace(/^http/, 'ws');\n\t\treturn `${base}/api/v1/public/ws/events`;\n\t}\n\n\tprivate async open(): Promise<void> {\n\t\tlet token: string;\n\t\ttry {\n\t\t\ttoken = await this.options.getToken();\n\t\t} catch {\n\t\t\tthis.scheduleReconnect();\n\t\t\treturn;\n\t\t}\n\t\tif (this.closed) return;\n\n\t\tconst WS = this.options.webSocket ?? WebSocket;\n\t\tconst ws = new WS(this.wsUrl());\n\t\tthis.ws = ws;\n\n\t\tws.onopen = () => {\n\t\t\tws.send(JSON.stringify({ type: 'auth', token }));\n\t\t\t// Auto-bound to the session's call; replay recovers a reconnect gap.\n\t\t\tws.send(\n\t\t\t\tJSON.stringify({ type: 'subscribe', ...(this.everConnected ? { replay: true } : {}) })\n\t\t\t);\n\t\t};\n\n\t\tws.onmessage = (message: MessageEvent) => {\n\t\t\tif (typeof message.data !== 'string') return;\n\t\t\tlet envelope: CallEventEnvelope;\n\t\t\ttry {\n\t\t\t\tenvelope = JSON.parse(message.data) as CallEventEnvelope;\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (typeof envelope.type !== 'string' || typeof envelope.seq !== 'number') return;\n\n\t\t\tif (envelope.type === 'subscribed') {\n\t\t\t\tthis.everConnected = true;\n\t\t\t\tthis.attempts = 0;\n\t\t\t\tthis.lastSeq = envelope.seq;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (envelope.type === 'pong' || envelope.type === 'unsubscribed') {\n\t\t\t\tthis.lastSeq = envelope.seq;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this.lastSeq > 0 && envelope.seq > this.lastSeq + 1) {\n\t\t\t\tthis.options.onGap?.(this.lastSeq + 1, envelope.seq);\n\t\t\t}\n\t\t\tthis.lastSeq = envelope.seq;\n\t\t\tthis.options.onEvent(envelope);\n\t\t};\n\n\t\tws.onclose = (event: CloseEvent) => {\n\t\t\tif (this.ws !== ws) return;\n\t\t\tthis.ws = null;\n\t\t\t// seq is per-connection — reset the tracker.\n\t\t\tthis.lastSeq = 0;\n\t\t\t// 4401/4403 are auth/scope rejections: retrying with the same session\n\t\t\t// can still succeed after a token refresh (getToken re-exchanges), but\n\t\t\t// a scope violation (4403) will never heal — stop there.\n\t\t\tif (event.code === 4403) {\n\t\t\t\tthis.closed = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.scheduleReconnect();\n\t\t};\n\n\t\tws.onerror = () => {\n\t\t\t// onclose follows and handles reconnection.\n\t\t};\n\t}\n\n\tprivate scheduleReconnect(): void {\n\t\tif (this.closed || this.reconnectTimer) return;\n\t\tconst delay = BACKOFF_MS[Math.min(this.attempts, BACKOFF_MS.length - 1)] ?? 8_000;\n\t\tthis.attempts += 1;\n\t\tthis.reconnectTimer = setTimeout(() => {\n\t\t\tthis.reconnectTimer = null;\n\t\t\tvoid this.open();\n\t\t}, delay);\n\t}\n}\n","/**\n * Wire types for the Talkif WebRTC signaling API and the pipecat SmallWebRTC\n * data-channel protocol spoken by Talkif voice bots.\n *\n * These shapes are the public contract. Field casing matches the backend JSON\n * exactly (camelCase over HTTP).\n */\n\n// ---------------------------------------------------------------------------\n// HTTP signaling\n// ---------------------------------------------------------------------------\n\nexport interface IceServer {\n\turls: string;\n\tusername?: string;\n\tcredential?: string;\n}\n\nexport interface IceServersResponse {\n\ticeServers: IceServer[];\n\t/** Seconds remaining on the underlying TURN credential. */\n\tttl: number;\n}\n\nexport interface CreateCallRequest {\n\tflowId: string;\n\tmetadata?: Record<string, unknown>;\n}\n\nexport interface CreateTestCallRequest {\n\tflowId: string;\n\t/**\n\t * Draft flow definition (frontend `FlowDefinition` shape). The library does\n\t * not interpret it — it is passed through to the backend, which validates\n\t * and compiles it for the bot.\n\t */\n\tdefinition: Record<string, unknown>;\n\tmetadata?: Record<string, unknown>;\n}\n\nexport type CallStatus =\n\t| 'CREATED'\n\t| 'BOT_ASSIGNED'\n\t| 'QUEUED'\n\t| 'RINGING'\n\t| 'IN_PROGRESS'\n\t| 'COMPLETED'\n\t| 'FAILED'\n\t| 'NO_ANSWER'\n\t| 'BUSY'\n\t| 'CANCELED';\n\n/** REST statuses that mean the call is genuinely over. */\nexport const TERMINAL_CALL_STATUSES: ReadonlySet<CallStatus> = new Set([\n\t'COMPLETED',\n\t'FAILED',\n\t'NO_ANSWER',\n\t'BUSY',\n\t'CANCELED',\n]);\n\nexport interface CreateCallResponse {\n\tcallId: string;\n\tflowId: string;\n\tstatus: CallStatus;\n\tbotId?: string | null;\n}\n\nexport interface WebRTCCall {\n\tcallId: string;\n\tstatus: CallStatus;\n}\n\nexport interface OfferRequest {\n\tsdp: string;\n\ticeServers?: IceServer[];\n}\n\n/** Public surface: token-exchange response (`POST /public/calls/session`). */\nexport interface PublicSessionResponse {\n\t/** Signed session token — sent as `Authorization: Bearer <token>`. */\n\ttoken: string;\n\t/** Seconds until the token expires (~15 min). */\n\texpiresIn: number;\n\t/** The flow this session may call (bound to the publishable key). */\n\tflowId: string;\n}\n\nexport interface OfferResponse {\n\tsdp: string;\n\tsdpType: string;\n\tbotId?: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Data-channel protocol (pipecat SmallWebRTC — bot side)\n// ---------------------------------------------------------------------------\n\n/** Bot → client: bot requests a new offer/answer cycle. */\nexport interface RenegotiateMessage {\n\ttype: 'renegotiate';\n}\n\n/** Bot → client: bot is closing the connection. */\nexport interface PeerLeftMessage {\n\ttype: 'peerLeft';\n}\n\n/** Client → bot: enable/disable a media receiver. 0=audio, 1=video, 2=screen. */\nexport interface TrackStatusMessage {\n\ttype: 'trackStatus';\n\treceiver_index: 0 | 1 | 2;\n\tenabled: boolean;\n}\n\nexport type SignallingPayload = RenegotiateMessage | PeerLeftMessage | TrackStatusMessage;\n\nexport interface SignallingEnvelope {\n\ttype: 'signalling';\n\tmessage: SignallingPayload;\n}\n\n/**\n * Anything on the data channel that is not the signalling envelope (and not a\n * `\"ping\"` keepalive string) is an app message: arbitrary JSON exchanged with\n * the bot pipeline.\n */\nexport type AppMessage = Record<string, unknown>;\n\n// ---------------------------------------------------------------------------\n// Realtime events WebSocket (`/api/v1/public/ws/events`)\n// ---------------------------------------------------------------------------\n\n/**\n * Server envelope on the realtime events WebSocket. `seq` is per-connection\n * and monotonic — a gap means THIS connection dropped frames under\n * backpressure, not that the call missed anything.\n */\nexport interface CallEventEnvelope {\n\tv: number;\n\ttype: string;\n\tcall_id?: string;\n\tseq: number;\n\tts: number;\n\tdata: Record<string, unknown>;\n}\n\n/** Event types the public WS delivers for the session's call. */\nexport type CallEventType =\n\t| 'transcript'\n\t| 'interim'\n\t| 'status'\n\t| 'tts'\n\t| 'tts_word'\n\t| 'speech'\n\t| 'turn'\n\t| 'node_transition'\n\t| 'error';\n\n// ---------------------------------------------------------------------------\n// Client state machine & events\n// ---------------------------------------------------------------------------\n\nexport type CallState =\n\t| 'idle'\n\t| 'requesting'\n\t| 'connecting'\n\t| 'connected'\n\t| 'ended'\n\t| 'error';\n\nexport interface TalkifCallEventMap {\n\t/** State machine transition. */\n\tstatechange: { state: CallState; previous: CallState };\n\t/** Media is flowing and the SDP answer is applied. */\n\tconnected: { callId: string; botId: string | null };\n\t/** Remote (bot) audio track arrived. */\n\ttrack: { stream: MediaStream };\n\t/** Call finished normally (user hangup, bot peerLeft, terminal status). */\n\tended: { reason: CallEndReason };\n\t/** Fatal failure. The call is torn down; state is 'error'. */\n\terror: { error: TalkifCallError };\n\t/** One-second duration tick while connected. */\n\ttick: { durationSecs: number };\n\t/** JSON app message from the bot over the data channel. */\n\tappmessage: { message: AppMessage };\n\t/** Bot asked for renegotiation. Emitted after the re-offer is attempted. */\n\trenegotiated: { ok: boolean };\n\t/** Mute toggled. */\n\tmutechange: { muted: boolean };\n\t/**\n\t * Any realtime event for this call from the events WebSocket (public mode).\n\t * Fires for every delivered envelope, including those with dedicated\n\t * events below.\n\t */\n\tcallevent: { event: CallEventEnvelope };\n\t/** Final transcript line (public mode, via the events WebSocket). */\n\ttranscript: { role: string; content: string; data: Record<string, unknown> };\n\t/** Interim (in-progress) transcription (public mode). May be dropped under backpressure. */\n\tinterim: { data: Record<string, unknown> };\n\t/**\n\t * Word-level TTS timing (public mode). Drives karaoke-style caption\n\t * highlighting; only words actually spoken are delivered. May be dropped\n\t * under backpressure.\n\t */\n\tttsword: TtsWordEvent;\n\t/**\n\t * Sentence-level chunk of the agent's reply (public mode), emitted when the\n\t * LLM output is handed to TTS synthesis — ahead of audio playback. Use for\n\t * chat-style streaming of the reply; reconcile with the final `transcript`\n\t * (on interruption the tail may never be spoken). May be dropped under\n\t * backpressure.\n\t */\n\tttschunk: TtsChunkEvent;\n}\n\n/** Payload of a `tts` realtime event. */\nexport interface TtsChunkEvent {\n\t/** Sentence-level text chunk of the agent's reply. */\n\ttext: string;\n\t/** Unix timestamp from the bot (seconds, fractional). */\n\ttimestamp: number;\n\t/** Full raw event payload (character counts, TTS timing metrics…). */\n\tdata: Record<string, unknown>;\n}\n\n/** Payload of a `tts_word` realtime event. */\nexport interface TtsWordEvent {\n\t/** The spoken word. */\n\tword: string;\n\t/** Presentation timestamp — when the word is spoken in the audio (ms), if known. */\n\tptsMs: number | null;\n\t/** Unix timestamp from the bot (seconds, fractional). */\n\ttimestamp: number;\n\t/** Full raw event payload. */\n\tdata: Record<string, unknown>;\n}\n\nexport type CallEndReason =\n\t| 'local-hangup'\n\t| 'peer-left'\n\t| 'terminal-status'\n\t| 'external';\n\nexport type TalkifCallErrorCode =\n\t| 'media-unavailable'\n\t| 'signaling-failed'\n\t| 'connection-failed'\n\t| 'pipeline-dead'\n\t| 'renegotiation-failed'\n\t/** Publishable key rejected, origin not allowed, or session expired. */\n\t| 'session-rejected'\n\t/** This session already has an active call (multi-tab). */\n\t| 'call-active'\n\t/** Rate/concurrency limit hit — retry later. */\n\t| 'rate-limited'\n\t/** No bot available right now — retry shortly. */\n\t| 'no-agent';\n\nexport class TalkifCallError extends Error {\n\treadonly code: TalkifCallErrorCode;\n\toverride readonly cause?: unknown;\n\n\tconstructor(code: TalkifCallErrorCode, message: string, cause?: unknown) {\n\t\tsuper(message);\n\t\tthis.name = 'TalkifCallError';\n\t\tthis.code = code;\n\t\tthis.cause = cause;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Configuration\n// ---------------------------------------------------------------------------\n\n/**\n * Supplies the Authorization header value for signaling requests.\n * Called per request so short-lived tokens (JWT refresh, widget session\n * tokens) stay current. Return e.g. `\"Bearer <token>\"`.\n */\nexport type AuthProvider = () => string | Promise<string>;\n\nexport interface TalkifClientConfig {\n\t/** API origin, e.g. `https://api.talkif.ai`. No trailing slash. */\n\tbaseUrl: string;\n\taccountId: string;\n\tauth: AuthProvider;\n\t/** Override fetch (tests, SSR polyfills). Defaults to global fetch. */\n\tfetch?: typeof fetch;\n}\n\n/**\n * Public (embeddable) configuration: the browser-safe publishable key from the\n * flow's public-access settings. The library exchanges it for a short-lived\n * session token itself — no bearer token, no account id. The page's Origin\n * must be on the key's allowlist.\n */\nexport interface TalkifPublicClientConfig {\n\t/** API origin, e.g. `https://api.talkif.ai`. No trailing slash. */\n\tbaseUrl: string;\n\t/** Publishable key (`pk_live_...`) — safe to ship in page source. */\n\tpublishableKey: string;\n\t/**\n\t * Cloudflare Turnstile sitekey (Talkif's — public, safe to ship). When set,\n\t * the library renders an invisible Turnstile challenge and produces a token\n\t * for each session exchange automatically. Leave unset to disable the gate\n\t * (dev/local, or before Turnstile is provisioned).\n\t */\n\tturnstileSiteKey?: string;\n\t/**\n\t * Manual Turnstile token supplier. Overrides `turnstileSiteKey` — supply\n\t * this only if you render Turnstile yourself. Called at each exchange.\n\t */\n\tturnstileToken?: () => string | Promise<string>;\n\t/** Override fetch (tests, SSR polyfills). Defaults to global fetch. */\n\tfetch?: typeof fetch;\n}\n\nexport type TalkifConfig = TalkifClientConfig | TalkifPublicClientConfig;\n\nexport function isPublicConfig(config: TalkifConfig): config is TalkifPublicClientConfig {\n\treturn 'publishableKey' in config;\n}\n\nexport interface StartCallOptions {\n\t/**\n\t * Start a call against a published flow. Required in authenticated mode.\n\t * Ignored in public mode — the flow is bound to the publishable key.\n\t */\n\tflowId?: string;\n\t/**\n\t * When set, uses the draft test-call endpoint with this definition instead\n\t * of the published flow.\n\t */\n\tdraftDefinition?: Record<string, unknown>;\n\tmetadata?: Record<string, unknown>;\n\t/** Constraints for getUserMedia. Defaults to `{ audio: true }`. */\n\taudio?: MediaTrackConstraints | boolean;\n\t/**\n\t * Attach remote audio to this element instead of an internally created\n\t * autoplay `Audio()`. Pass `null` to disable playback entirely (consumer\n\t * handles the `track` event itself).\n\t */\n\taudioElement?: HTMLAudioElement | null;\n}\n","/**\n * Cloudflare Turnstile integration — the \"prove you're a real browser\" gate\n * that stops scripted abuse of the public call surface.\n *\n * The customer does nothing: the widget self-injects Cloudflare's script,\n * renders an invisible Turnstile widget with Talkif's sitekey, and produces a\n * fresh token for each session exchange. The sitekey is public and safe to\n * ship; the matching secret lives only in the backend.\n */\n\nconst TURNSTILE_SCRIPT_URL =\n\t'https://challenges.cloudflare.com/turnstile/v0/api.js';\n\n/** Minimal shape of the global `turnstile` object we use. */\ninterface TurnstileApi {\n\trender(\n\t\tcontainer: HTMLElement,\n\t\toptions: {\n\t\t\tsitekey: string;\n\t\t\tcallback?: (token: string) => void;\n\t\t\t'error-callback'?: (code?: string) => void;\n\t\t\t'expired-callback'?: () => void;\n\t\t\tsize?: 'invisible' | 'normal' | 'compact' | 'flexible';\n\t\t\texecution?: 'render' | 'execute';\n\t\t\tretry?: 'auto' | 'never';\n\t\t}\n\t): string;\n\texecute(widgetId: string, options?: { sitekey?: string }): void;\n\treset(widgetId: string): void;\n\tremove(widgetId: string): void;\n}\n\ndeclare global {\n\tinterface Window {\n\t\tturnstile?: TurnstileApi;\n\t}\n}\n\nlet scriptPromise: Promise<void> | null = null;\n\n/** Load Cloudflare's Turnstile script once, shared across all instances. */\nfunction loadScript(): Promise<void> {\n\tif (typeof document === 'undefined') {\n\t\treturn Promise.reject(new Error('Turnstile requires a browser environment'));\n\t}\n\tif (window.turnstile) return Promise.resolve();\n\tscriptPromise ??= new Promise<void>((resolve, reject) => {\n\t\tconst existing = document.querySelector<HTMLScriptElement>(\n\t\t\t`script[src=\"${TURNSTILE_SCRIPT_URL}\"]`\n\t\t);\n\t\tif (existing) {\n\t\t\texisting.addEventListener('load', () => resolve());\n\t\t\texisting.addEventListener('error', () => reject(new Error('Turnstile script failed to load')));\n\t\t\tif (window.turnstile) resolve();\n\t\t\treturn;\n\t\t}\n\t\tconst script = document.createElement('script');\n\t\tscript.src = TURNSTILE_SCRIPT_URL;\n\t\tscript.async = true;\n\t\tscript.defer = true;\n\t\tscript.onload = () => resolve();\n\t\tscript.onerror = () => reject(new Error('Turnstile script failed to load'));\n\t\tdocument.head.appendChild(script);\n\t});\n\treturn scriptPromise;\n}\n\n/**\n * Manages one invisible Turnstile widget and hands out fresh tokens.\n *\n * Turnstile tokens are single-use and short-lived, so we `execute()` a fresh\n * challenge for each call to `getToken()` (i.e. each session exchange). The\n * widget is rendered lazily on first use and torn down with `dispose()`.\n */\nexport class TurnstileManager {\n\tprivate readonly siteKey: string;\n\tprivate container: HTMLDivElement | null = null;\n\tprivate widgetId: string | null = null;\n\tprivate pending: {\n\t\tresolve: (token: string) => void;\n\t\treject: (err: Error) => void;\n\t} | null = null;\n\n\tconstructor(siteKey: string) {\n\t\tthis.siteKey = siteKey;\n\t}\n\n\t/** Obtain a fresh Turnstile token. Rejects if the challenge fails. */\n\tasync getToken(): Promise<string> {\n\t\tawait loadScript();\n\t\tconst turnstile = window.turnstile;\n\t\tif (!turnstile) throw new Error('Turnstile unavailable after script load');\n\n\t\tif (!this.container) {\n\t\t\t// A hidden, off-screen host — the invisible widget never shows UI\n\t\t\t// but Cloudflare still requires an element to render into.\n\t\t\tthis.container = document.createElement('div');\n\t\t\tthis.container.style.position = 'fixed';\n\t\t\tthis.container.style.width = '0';\n\t\t\tthis.container.style.height = '0';\n\t\t\tthis.container.style.overflow = 'hidden';\n\t\t\tthis.container.style.pointerEvents = 'none';\n\t\t\tthis.container.setAttribute('aria-hidden', 'true');\n\t\t\tdocument.body.appendChild(this.container);\n\t\t}\n\n\t\tif (this.widgetId === null) {\n\t\t\tthis.widgetId = turnstile.render(this.container, {\n\t\t\t\tsitekey: this.siteKey,\n\t\t\t\tsize: 'invisible',\n\t\t\t\texecution: 'execute',\n\t\t\t\tretry: 'auto',\n\t\t\t\tcallback: (token: string) => {\n\t\t\t\t\tthis.pending?.resolve(token);\n\t\t\t\t\tthis.pending = null;\n\t\t\t\t},\n\t\t\t\t'error-callback': (code?: string) => {\n\t\t\t\t\tthis.pending?.reject(new Error(`Turnstile challenge failed${code ? `: ${code}` : ''}`));\n\t\t\t\t\tthis.pending = null;\n\t\t\t\t},\n\t\t\t\t'expired-callback': () => {\n\t\t\t\t\t// Token expired before use — the next getToken() re-executes.\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\treturn new Promise<string>((resolve, reject) => {\n\t\t\t// Only one outstanding challenge at a time; supersede any prior.\n\t\t\tif (this.pending) {\n\t\t\t\tthis.pending.reject(new Error('Turnstile challenge superseded'));\n\t\t\t}\n\t\t\tthis.pending = { resolve, reject };\n\t\t\tconst id = this.widgetId;\n\t\t\tif (id === null) {\n\t\t\t\treject(new Error('Turnstile widget not rendered'));\n\t\t\t\tthis.pending = null;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tturnstile.reset(id);\n\t\t\t\tturnstile.execute(id, { sitekey: this.siteKey });\n\t\t\t} catch (err) {\n\t\t\t\tthis.pending = null;\n\t\t\t\treject(err instanceof Error ? err : new Error('Turnstile execute failed'));\n\t\t\t}\n\t\t});\n\t}\n\n\tdispose(): void {\n\t\tif (this.widgetId !== null && window.turnstile) {\n\t\t\ttry {\n\t\t\t\twindow.turnstile.remove(this.widgetId);\n\t\t\t} catch {\n\t\t\t\t// best-effort\n\t\t\t}\n\t\t}\n\t\tthis.widgetId = null;\n\t\tthis.pending = null;\n\t\tif (this.container) {\n\t\t\tthis.container.remove();\n\t\t\tthis.container = null;\n\t\t}\n\t}\n}\n","import type {\n\tCreateCallRequest,\n\tCreateCallResponse,\n\tCreateTestCallRequest,\n\tIceServersResponse,\n\tOfferRequest,\n\tOfferResponse,\n\tPublicSessionResponse,\n\tTalkifConfig,\n\tTalkifPublicClientConfig,\n\tWebRTCCall,\n} from './types.js';\nimport { isPublicConfig, TalkifCallError } from './types.js';\nimport { TurnstileManager } from './turnstile.js';\n\nexport class SignalingError extends Error {\n\treadonly status: number;\n\treadonly body: unknown;\n\n\tconstructor(status: number, message: string, body: unknown) {\n\t\tsuper(message);\n\t\tthis.name = 'SignalingError';\n\t\tthis.status = status;\n\t\tthis.body = body;\n\t}\n}\n\n/** Re-exchange the publishable key this many ms before the token expires. */\nconst SESSION_REFRESH_MARGIN_MS = 60_000;\n\n/**\n * Holds the short-lived session token for the public surface: exchanges the\n * publishable key on first use, re-exchanges near expiry, and can be\n * invalidated on a 401 so the next request retries with a fresh token.\n */\nexport class PublicSession {\n\tprivate readonly config: TalkifPublicClientConfig;\n\tprivate token: string | null = null;\n\tprivate expiresAt = 0;\n\tprivate flowIdValue: string | null = null;\n\tprivate inflight: Promise<string> | null = null;\n\tprivate turnstile: TurnstileManager | null = null;\n\n\tconstructor(config: TalkifPublicClientConfig) {\n\t\tthis.config = config;\n\t\t// Built-in Turnstile: render an invisible challenge from the sitekey,\n\t\t// unless the caller supplies their own token provider.\n\t\tif (config.turnstileSiteKey && !config.turnstileToken) {\n\t\t\tthis.turnstile = new TurnstileManager(config.turnstileSiteKey);\n\t\t}\n\t}\n\n\t/** A fresh Turnstile token, from the manual supplier or the built-in\n\t * manager; undefined when no gate is configured (dev/local). */\n\tprivate async turnstileToken(): Promise<string | undefined> {\n\t\tif (this.config.turnstileToken) {\n\t\t\treturn this.config.turnstileToken();\n\t\t}\n\t\tif (this.turnstile) {\n\t\t\treturn this.turnstile.getToken();\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/** Tear down the Turnstile widget (call when the client is disposed). */\n\tdispose(): void {\n\t\tthis.turnstile?.dispose();\n\t\tthis.turnstile = null;\n\t}\n\n\t/** Flow bound to the publishable key (known after the first exchange). */\n\tget flowId(): string | null {\n\t\treturn this.flowIdValue;\n\t}\n\n\tasync getToken(): Promise<string> {\n\t\tif (this.token && Date.now() < this.expiresAt - SESSION_REFRESH_MARGIN_MS) {\n\t\t\treturn this.token;\n\t\t}\n\t\t// Single-flight the exchange: concurrent requests share one round-trip.\n\t\tthis.inflight ??= this.exchange().finally(() => {\n\t\t\tthis.inflight = null;\n\t\t});\n\t\treturn this.inflight;\n\t}\n\n\tinvalidate(): void {\n\t\tthis.token = null;\n\t\tthis.expiresAt = 0;\n\t}\n\n\tprivate async exchange(): Promise<string> {\n\t\tconst doFetch = this.config.fetch ?? fetch;\n\t\tconst turnstileToken = await this.turnstileToken();\n\t\tconst response = await doFetch(\n\t\t\t`${this.config.baseUrl.replace(/\\/$/, '')}/api/v1/public/calls/session`,\n\t\t\t{\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tpublishableKey: this.config.publishableKey,\n\t\t\t\t\t...(turnstileToken !== undefined ? { turnstileToken } : {}),\n\t\t\t\t}),\n\t\t\t}\n\t\t);\n\t\tconst parsed: unknown = await response.json().catch(() => null);\n\t\tif (!response.ok) {\n\t\t\tthrow new SignalingError(\n\t\t\t\tresponse.status,\n\t\t\t\t'Session exchange failed — check the publishable key and that this origin is on its allowlist',\n\t\t\t\tparsed\n\t\t\t);\n\t\t}\n\t\tconst session = parsed as PublicSessionResponse;\n\t\tthis.token = session.token;\n\t\tthis.expiresAt = Date.now() + session.expiresIn * 1_000;\n\t\tthis.flowIdValue = session.flowId;\n\t\treturn session.token;\n\t}\n}\n\n/**\n * Thin HTTP client for the Talkif WebRTC signaling routes. Two modes:\n * - authenticated (dashboard/API): `/api/v1/accounts/{accountId}/calls/webrtc`\n * - public (embed): `/api/v1/public/calls`, session token minted from the\n * publishable key and refreshed transparently.\n */\nexport class SignalingClient {\n\tprivate readonly config: TalkifConfig;\n\treadonly session: PublicSession | null;\n\n\tconstructor(config: TalkifConfig) {\n\t\tthis.config = config;\n\t\tthis.session = isPublicConfig(config) ? new PublicSession(config) : null;\n\t}\n\n\tget isPublic(): boolean {\n\t\treturn this.session !== null;\n\t}\n\n\tprivate get base(): string {\n\t\tconst baseUrl = this.config.baseUrl.replace(/\\/$/, '');\n\t\tif (isPublicConfig(this.config)) {\n\t\t\treturn `${baseUrl}/api/v1/public/calls`;\n\t\t}\n\t\treturn `${baseUrl}/api/v1/accounts/${this.config.accountId}/calls/webrtc`;\n\t}\n\n\tprivate async authorization(): Promise<string> {\n\t\tif (this.session) {\n\t\t\treturn `Bearer ${await this.session.getToken()}`;\n\t\t}\n\t\treturn (this.config as { auth: () => string | Promise<string> }).auth();\n\t}\n\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST',\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\tretriedAuth = false\n\t): Promise<T> {\n\t\tconst doFetch = this.config.fetch ?? fetch;\n\t\tconst authorization = await this.authorization();\n\t\tconst response = await doFetch(`${this.base}${path}`, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\tauthorization,\n\t\t\t\t...(body !== undefined ? { 'content-type': 'application/json' } : {}),\n\t\t\t},\n\t\t\t...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n\t\t});\n\n\t\t// Public mode: an expired/rotated session token gets one transparent\n\t\t// re-exchange + retry before surfacing the failure.\n\t\tif (response.status === 401 && this.session && !retriedAuth) {\n\t\t\tthis.session.invalidate();\n\t\t\treturn this.request(method, path, body, true);\n\t\t}\n\n\t\tlet parsed: unknown = null;\n\t\tconst text = await response.text();\n\t\tif (text) {\n\t\t\ttry {\n\t\t\t\tparsed = JSON.parse(text);\n\t\t\t} catch {\n\t\t\t\tparsed = text;\n\t\t\t}\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst message =\n\t\t\t\ttypeof parsed === 'object' && parsed !== null && 'detail' in parsed\n\t\t\t\t\t? String((parsed as { detail: unknown }).detail)\n\t\t\t\t\t: typeof parsed === 'object' && parsed !== null && 'message' in parsed\n\t\t\t\t\t\t? String((parsed as { message: unknown }).message)\n\t\t\t\t\t\t: `Signaling request failed: ${method} ${path} → ${response.status}`;\n\t\t\tthrow new SignalingError(response.status, message, parsed);\n\t\t}\n\t\treturn parsed as T;\n\t}\n\n\tcreateCall(body: CreateCallRequest): Promise<CreateCallResponse> {\n\t\tif (this.isPublic) {\n\t\t\t// Flow is bound to the publishable key server-side; body is empty.\n\t\t\treturn this.request('POST', '/calls', {});\n\t\t}\n\t\treturn this.request('POST', '', body);\n\t}\n\n\tcreateTestCall(body: CreateTestCallRequest): Promise<CreateCallResponse> {\n\t\tif (this.isPublic) {\n\t\t\tthrow new TalkifCallError(\n\t\t\t\t'signaling-failed',\n\t\t\t\t'Draft test calls are not available on the public surface'\n\t\t\t);\n\t\t}\n\t\treturn this.request('POST', '/test', body);\n\t}\n\n\tgetIceServers(): Promise<IceServersResponse> {\n\t\treturn this.request('GET', '/ice-servers');\n\t}\n\n\tsendOffer(callId: string, body: OfferRequest): Promise<OfferResponse> {\n\t\tconst path = this.isPublic ? `/calls/${callId}/offer` : `/${callId}/offer`;\n\t\treturn this.request('POST', path, body);\n\t}\n\n\tgetCall(callId: string): Promise<WebRTCCall> {\n\t\tconst path = this.isPublic ? `/calls/${callId}` : `/${callId}`;\n\t\treturn this.request('GET', path);\n\t}\n}\n\n/** Map a signaling failure to the library's typed error. */\nexport function asSignalingCallError(error: unknown): TalkifCallError {\n\tif (error instanceof TalkifCallError) return error;\n\tif (error instanceof SignalingError) {\n\t\tconst code =\n\t\t\terror.status === 401\n\t\t\t\t? 'session-rejected'\n\t\t\t\t: error.status === 409\n\t\t\t\t\t? 'call-active'\n\t\t\t\t\t: error.status === 429\n\t\t\t\t\t\t? 'rate-limited'\n\t\t\t\t\t\t: error.status === 503\n\t\t\t\t\t\t\t? 'no-agent'\n\t\t\t\t\t\t\t: 'signaling-failed';\n\t\treturn new TalkifCallError(code, error.message, error);\n\t}\n\tconst message = error instanceof Error ? error.message : 'Signaling request failed';\n\treturn new TalkifCallError('signaling-failed', message, error);\n}\n","import { DataChannel } from './data-channel.js';\nimport { Emitter } from './emitter.js';\nimport { CallEventsClient } from './events.js';\nimport { asSignalingCallError, SignalingClient } from './signaling.js';\nimport type {\n\tAppMessage,\n\tCallEndReason,\n\tCallEventEnvelope,\n\tCallState,\n\tIceServer,\n\tSignallingPayload,\n\tStartCallOptions,\n\tTalkifCallEventMap,\n\tTalkifConfig,\n} from './types.js';\nimport { TalkifCallError, TERMINAL_CALL_STATUSES } from './types.js';\n\n// If no external health confirmation (SSE/app message) arrives within this\n// window after media connects, verify call health against the backend over\n// REST before tearing down. Silence alone is NOT proof of pipeline death.\nconst PIPELINE_HEALTH_TIMEOUT_MS = 15_000;\n\n// Safety net for ICE gathering: send whatever we have if no relay candidate\n// shows up. With iceTransportPolicy 'relay' the first relay(udp) candidate is\n// all the bot needs; TURN tcp/443 allocations routinely never complete, so\n// waiting for iceGatheringState === 'complete' would stall every call.\nconst ICE_FIRST_RELAY_TIMEOUT_MS = 1_500;\n\n/**\n * One WebRTC voice session with a Talkif agent.\n *\n * Lifecycle: `idle → requesting → connecting → connected → ended | error`.\n * A TalkifCall instance is single-use: create a new one per call.\n */\nexport class TalkifCall {\n\tprivate readonly signaling: SignalingClient;\n\tprivate readonly config: TalkifConfig;\n\tprivate readonly emitter = new Emitter<TalkifCallEventMap>();\n\tprivate events: CallEventsClient | null = null;\n\n\tprivate pc: RTCPeerConnection | null = null;\n\tprivate localStream: MediaStream | null = null;\n\tprivate remoteAudio: HTMLAudioElement | null = null;\n\tprivate ownsAudioElement = false;\n\tprivate dataChannel: DataChannel | null = null;\n\tprivate iceServers: IceServer[] = [];\n\n\tprivate durationTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate healthTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate pipelineConfirmed = false;\n\tprivate renegotiating = false;\n\n\tprivate _state: CallState = 'idle';\n\tprivate _callId: string | null = null;\n\tprivate _botId: string | null = null;\n\tprivate _muted = false;\n\tprivate _durationSecs = 0;\n\n\tconstructor(config: TalkifConfig) {\n\t\tthis.config = config;\n\t\tthis.signaling = new SignalingClient(config);\n\t}\n\n\t// -- public surface -------------------------------------------------------\n\n\tget state(): CallState {\n\t\treturn this._state;\n\t}\n\n\tget callId(): string | null {\n\t\treturn this._callId;\n\t}\n\n\tget botId(): string | null {\n\t\treturn this._botId;\n\t}\n\n\tget muted(): boolean {\n\t\treturn this._muted;\n\t}\n\n\tget durationSecs(): number {\n\t\treturn this._durationSecs;\n\t}\n\n\ton<K extends keyof TalkifCallEventMap>(\n\t\tevent: K,\n\t\tlistener: (payload: TalkifCallEventMap[K]) => void\n\t): () => void {\n\t\treturn this.emitter.on(event, listener);\n\t}\n\n\toff<K extends keyof TalkifCallEventMap>(\n\t\tevent: K,\n\t\tlistener: (payload: TalkifCallEventMap[K]) => void\n\t): void {\n\t\tthis.emitter.off(event, listener);\n\t}\n\n\t/**\n\t * External health signal (e.g. the dashboard's SSE layer saw a\n\t * RUNNING/CONNECTED status). Cancels the REST health watchdog.\n\t */\n\tconfirmPipelineHealthy(): void {\n\t\tthis.pipelineConfirmed = true;\n\t\tif (this.healthTimer) {\n\t\t\tclearTimeout(this.healthTimer);\n\t\t\tthis.healthTimer = null;\n\t\t}\n\t}\n\n\t/**\n\t * External terminal signal (e.g. SSE reported COMPLETED). Tears down and\n\t * transitions to 'ended'.\n\t */\n\tendedExternally(): void {\n\t\tif (this._state !== 'connecting' && this._state !== 'connected') return;\n\t\tthis.teardown();\n\t\tthis.setState('ended');\n\t\tthis.emitter.emit('ended', { reason: 'external' });\n\t}\n\n\t/** Send an arbitrary JSON app message to the bot over the data channel. */\n\tsendAppMessage(message: AppMessage): boolean {\n\t\treturn this.dataChannel?.sendAppMessage(message) ?? false;\n\t}\n\n\tsetMuted(muted: boolean): void {\n\t\tif (!this.localStream) return;\n\t\tfor (const track of this.localStream.getAudioTracks()) {\n\t\t\ttrack.enabled = !muted;\n\t\t}\n\t\tthis._muted = muted;\n\t\tthis.emitter.emit('mutechange', { muted });\n\t}\n\n\t/** User-initiated hangup. */\n\thangup(): void {\n\t\tif (this._state === 'idle' || this._state === 'ended' || this._state === 'error') return;\n\t\tthis.teardown();\n\t\tthis.setState('ended');\n\t\tthis.emitter.emit('ended', { reason: 'local-hangup' });\n\t}\n\n\t/** Full teardown regardless of state — safe to call multiple times. */\n\tdispose(): void {\n\t\tthis.teardown();\n\t\tthis.signaling.session?.dispose();\n\t\tthis.emitter.removeAll();\n\t}\n\n\t// -- lifecycle ------------------------------------------------------------\n\n\tasync start(options: StartCallOptions): Promise<void> {\n\t\tif (this._state !== 'idle') {\n\t\t\tthrow new TalkifCallError('signaling-failed', `Cannot start a call from state '${this._state}' — create a new TalkifCall`);\n\t\t}\n\t\tthis.setState('requesting');\n\n\t\tif (!this.signaling.isPublic && !options.flowId) {\n\t\t\tthrow new TalkifCallError('signaling-failed', 'flowId is required in authenticated mode');\n\t\t}\n\n\t\ttry {\n\t\t\t// Run bot warmup (create call → bot assignment, 2-5s) in parallel with\n\t\t\t// local setup (ICE fetch, mic, offer, candidate gathering).\n\t\t\tconst flowId = options.flowId ?? '';\n\t\t\tconst [callResponse, local] = await Promise.all([\n\t\t\t\toptions.draftDefinition !== undefined\n\t\t\t\t\t? this.signaling.createTestCall({\n\t\t\t\t\t\t\tflowId,\n\t\t\t\t\t\t\tdefinition: options.draftDefinition,\n\t\t\t\t\t\t\t...(options.metadata !== undefined ? { metadata: options.metadata } : {}),\n\t\t\t\t\t\t})\n\t\t\t\t\t: this.signaling.createCall({\n\t\t\t\t\t\t\tflowId,\n\t\t\t\t\t\t\t...(options.metadata !== undefined ? { metadata: options.metadata } : {}),\n\t\t\t\t\t\t}),\n\t\t\t\tthis.prepareLocal(options),\n\t\t\t]);\n\n\t\t\tthis._callId = callResponse.callId;\n\t\t\tthis.pc = local.pc;\n\t\t\tthis.localStream = local.stream;\n\t\t\tthis.setState('connecting');\n\n\t\t\tthis.pc.ontrack = (event) => {\n\t\t\t\tconst stream = event.streams[0];\n\t\t\t\tif (!stream) return;\n\t\t\t\tthis.attachRemoteAudio(stream, options);\n\t\t\t\tthis.emitter.emit('track', { stream });\n\t\t\t};\n\n\t\t\tthis.pc.onconnectionstatechange = () => {\n\t\t\t\tconst state = this.pc?.connectionState;\n\t\t\t\tif (state === 'failed' || state === 'disconnected') {\n\t\t\t\t\tthis.fail(new TalkifCallError('connection-failed', 'WebRTC connection failed'));\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tthis.dataChannel = new DataChannel(this.pc, {\n\t\t\t\tonSignalling: (message) => this.handleSignalling(message),\n\t\t\t\tonAppMessage: (message) => this.emitter.emit('appmessage', { message }),\n\t\t\t});\n\n\t\t\tconst answer = await this.signaling.sendOffer(callResponse.callId, {\n\t\t\t\tsdp: this.mustLocalSdp(),\n\t\t\t\ticeServers: this.iceServers,\n\t\t\t});\n\t\t\tawait this.pc.setRemoteDescription({ type: 'answer', sdp: answer.sdp });\n\t\t\tthis._botId = answer.botId ?? null;\n\n\t\t\tthis.setState('connected');\n\t\t\tthis.emitter.emit('connected', { callId: callResponse.callId, botId: this._botId });\n\t\t\tthis.startDurationTimer();\n\t\t\tthis.armHealthWatchdog(callResponse.callId);\n\t\t\tthis.connectEvents();\n\t\t} catch (error) {\n\t\t\tthis.fail(\n\t\t\t\terror instanceof TalkifCallError ? error : asSignalingCallError(error)\n\t\t\t);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate async prepareLocal(options: StartCallOptions): Promise<{ pc: RTCPeerConnection; stream: MediaStream }> {\n\t\tconst { iceServers } = await this.signaling.getIceServers();\n\t\tthis.iceServers = iceServers;\n\n\t\tif (!navigator.mediaDevices?.getUserMedia) {\n\t\t\tthrow new TalkifCallError('media-unavailable', 'Microphone access unavailable. This requires HTTPS or localhost.');\n\t\t}\n\t\tconst stream = await navigator.mediaDevices\n\t\t\t.getUserMedia({ audio: options.audio ?? true })\n\t\t\t.catch((cause: unknown) => {\n\t\t\t\tthrow new TalkifCallError('media-unavailable', 'Microphone permission denied or unavailable', cause);\n\t\t\t});\n\n\t\t// Relay-only transport: skips host/srflx gathering (no dead-interface\n\t\t// timeouts), works through firewalls that block UDP, avoids TURN\n\t\t// \"Forbidden IP\" errors for private IPs, and gathers in ~100-200ms.\n\t\tconst pc = new RTCPeerConnection({\n\t\t\ticeServers: iceServers.map((s) => ({\n\t\t\t\turls: s.urls,\n\t\t\t\t...(s.username !== undefined ? { username: s.username } : {}),\n\t\t\t\t...(s.credential !== undefined ? { credential: s.credential } : {}),\n\t\t\t})),\n\t\t\ticeTransportPolicy: 'relay',\n\t\t});\n\t\tfor (const track of stream.getTracks()) {\n\t\t\tpc.addTrack(track, stream);\n\t\t}\n\n\t\tconst offer = await pc.createOffer();\n\t\tawait pc.setLocalDescription(offer);\n\t\tif (!offer.sdp) {\n\t\t\tthrow new TalkifCallError('signaling-failed', 'Failed to create SDP offer');\n\t\t}\n\n\t\tawait this.waitForFirstRelayCandidate(pc);\n\t\treturn { pc, stream };\n\t}\n\n\tprivate waitForFirstRelayCandidate(pc: RTCPeerConnection): Promise<void> {\n\t\treturn new Promise<void>((resolve) => {\n\t\t\tif (pc.iceGatheringState === 'complete') {\n\t\t\t\tresolve();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet settled = false;\n\t\t\tconst finish = () => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tpc.removeEventListener('icecandidate', onCandidate);\n\t\t\t\tpc.removeEventListener('icegatheringstatechange', onGatheringComplete);\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tconst timeout = setTimeout(finish, ICE_FIRST_RELAY_TIMEOUT_MS);\n\t\t\tconst onCandidate = (e: RTCPeerConnectionIceEvent) => {\n\t\t\t\tif (e.candidate && e.candidate.type === 'relay') finish();\n\t\t\t};\n\t\t\tconst onGatheringComplete = () => {\n\t\t\t\tif (pc.iceGatheringState === 'complete') finish();\n\t\t\t};\n\t\t\tpc.addEventListener('icecandidate', onCandidate);\n\t\t\tpc.addEventListener('icegatheringstatechange', onGatheringComplete);\n\t\t});\n\t}\n\n\tprivate attachRemoteAudio(stream: MediaStream, options: StartCallOptions): void {\n\t\tif (options.audioElement === null) return; // consumer opted out\n\t\tif (!this.remoteAudio) {\n\t\t\tif (options.audioElement) {\n\t\t\t\tthis.remoteAudio = options.audioElement;\n\t\t\t\tthis.ownsAudioElement = false;\n\t\t\t} else {\n\t\t\t\tthis.remoteAudio = new Audio();\n\t\t\t\tthis.remoteAudio.autoplay = true;\n\t\t\t\tthis.ownsAudioElement = true;\n\t\t\t}\n\t\t}\n\t\tthis.remoteAudio.srcObject = stream;\n\t}\n\n\t// -- data-channel protocol ------------------------------------------------\n\n\tprivate handleSignalling(message: SignallingPayload): void {\n\t\tswitch (message.type) {\n\t\t\tcase 'peerLeft':\n\t\t\t\tif (this._state === 'connected' || this._state === 'connecting') {\n\t\t\t\t\tthis.teardown();\n\t\t\t\t\tthis.setState('ended');\n\t\t\t\t\tthis.emitter.emit('ended', { reason: 'peer-left' });\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'renegotiate':\n\t\t\t\tvoid this.renegotiate();\n\t\t\t\tbreak;\n\t\t\tcase 'trackStatus':\n\t\t\t\t// Bot→client trackStatus is not part of the current protocol; ignore.\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Bot asked for a new offer/answer cycle. Re-offer through the same relay\n\t * endpoint. If the backend rejects re-offers (older deployments), the call\n\t * keeps running on the existing session — surfaced via `renegotiated`.\n\t */\n\tprivate async renegotiate(): Promise<void> {\n\t\tif (!this.pc || !this._callId || this.renegotiating) return;\n\t\tthis.renegotiating = true;\n\t\ttry {\n\t\t\tconst offer = await this.pc.createOffer({ iceRestart: true });\n\t\t\tawait this.pc.setLocalDescription(offer);\n\t\t\tawait this.waitForFirstRelayCandidate(this.pc);\n\t\t\tconst answer = await this.signaling.sendOffer(this._callId, {\n\t\t\t\tsdp: this.mustLocalSdp(),\n\t\t\t\ticeServers: this.iceServers,\n\t\t\t});\n\t\t\tawait this.pc.setRemoteDescription({ type: 'answer', sdp: answer.sdp });\n\t\t\tthis.emitter.emit('renegotiated', { ok: true });\n\t\t} catch {\n\t\t\tthis.emitter.emit('renegotiated', { ok: false });\n\t\t} finally {\n\t\t\tthis.renegotiating = false;\n\t\t}\n\t}\n\n\t// -- realtime events (public mode) ----------------------------------------\n\n\t/**\n\t * Public mode only: open the realtime events WebSocket for this call.\n\t * Events double as the pipeline-health signal, and a terminal `status`\n\t * event ends the call from the server side (e.g. max-duration reached).\n\t */\n\tprivate connectEvents(): void {\n\t\tconst session = this.signaling.session;\n\t\tif (!session) return; // authenticated mode: dashboard SSE covers events\n\n\t\tthis.events = new CallEventsClient({\n\t\t\tbaseUrl: this.config.baseUrl,\n\t\t\tgetToken: () => session.getToken(),\n\t\t\tonEvent: (event) => this.handleCallEvent(event),\n\t\t});\n\t\tthis.events.connect();\n\t}\n\n\tprivate handleCallEvent(event: CallEventEnvelope): void {\n\t\t// Events flowing at all = the bot pipeline is alive.\n\t\tthis.confirmPipelineHealthy();\n\t\tthis.emitter.emit('callevent', { event });\n\n\t\tswitch (event.type) {\n\t\t\tcase 'transcript': {\n\t\t\t\tconst role = typeof event.data.role === 'string' ? event.data.role : 'unknown';\n\t\t\t\tconst content = typeof event.data.content === 'string' ? event.data.content : '';\n\t\t\t\tthis.emitter.emit('transcript', { role, content, data: event.data });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'interim':\n\t\t\t\tthis.emitter.emit('interim', { data: event.data });\n\t\t\t\tbreak;\n\t\t\tcase 'tts': {\n\t\t\t\tconst text = typeof event.data.text === 'string' ? event.data.text : '';\n\t\t\t\tif (!text) break;\n\t\t\t\tconst timestamp = typeof event.data.timestamp === 'number' ? event.data.timestamp : 0;\n\t\t\t\tthis.emitter.emit('ttschunk', { text, timestamp, data: event.data });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'tts_word': {\n\t\t\t\tconst word = typeof event.data.word === 'string' ? event.data.word : '';\n\t\t\t\tif (!word) break;\n\t\t\t\tconst ptsMs = typeof event.data.pts_ms === 'number' ? event.data.pts_ms : null;\n\t\t\t\tconst timestamp = typeof event.data.timestamp === 'number' ? event.data.timestamp : 0;\n\t\t\t\tthis.emitter.emit('ttsword', { word, ptsMs, timestamp, data: event.data });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'status': {\n\t\t\t\tconst status = typeof event.data.status === 'string' ? event.data.status : '';\n\t\t\t\tif (TERMINAL_CALL_STATUSES.has(status.toUpperCase() as never)) {\n\t\t\t\t\tthis.endedExternally();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// -- health watchdog ------------------------------------------------------\n\n\tprivate armHealthWatchdog(callId: string): void {\n\t\tthis.pipelineConfirmed = false;\n\t\tthis.healthTimer = setTimeout(() => {\n\t\t\tthis.healthTimer = null;\n\t\t\tif (this.pipelineConfirmed) return;\n\t\t\tvoid this.verifyPipelineHealth(callId);\n\t\t}, PIPELINE_HEALTH_TIMEOUT_MS);\n\t}\n\n\tprivate async verifyPipelineHealth(callId: string): Promise<void> {\n\t\ttry {\n\t\t\tconst call = await this.signaling.getCall(callId);\n\t\t\tif (this.pipelineConfirmed || !this.pc) return;\n\t\t\tif (TERMINAL_CALL_STATUSES.has(call.status)) {\n\t\t\t\tthis.fail(new TalkifCallError('pipeline-dead', `Backend reports terminal status '${call.status}'`));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Backend says the call is alive — the silence was the event layer's\n\t\t\t// fault, not the pipeline's.\n\t\t\tthis.pipelineConfirmed = true;\n\t\t} catch {\n\t\t\tif (this.pipelineConfirmed || !this.pc) return;\n\t\t\t// REST unreachable — fall back to the media-plane signal. Flowing\n\t\t\t// audio means alive; later media death is caught by\n\t\t\t// onconnectionstatechange. Kill only when both signals are gone.\n\t\t\tif (this.pc.connectionState === 'connected') {\n\t\t\t\tthis.pipelineConfirmed = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.fail(new TalkifCallError('pipeline-dead', 'No health signal, REST unreachable, media not connected'));\n\t\t}\n\t}\n\n\t// -- internals ------------------------------------------------------------\n\n\tprivate mustLocalSdp(): string {\n\t\tconst sdp = this.pc?.localDescription?.sdp;\n\t\tif (!sdp) {\n\t\t\tthrow new TalkifCallError('signaling-failed', 'Local SDP missing');\n\t\t}\n\t\treturn sdp;\n\t}\n\n\tprivate startDurationTimer(): void {\n\t\tthis._durationSecs = 0;\n\t\tthis.durationTimer = setInterval(() => {\n\t\t\tthis._durationSecs += 1;\n\t\t\tthis.emitter.emit('tick', { durationSecs: this._durationSecs });\n\t\t}, 1_000);\n\t}\n\n\tprivate fail(error: TalkifCallError): void {\n\t\tif (this._state === 'ended' || this._state === 'error') return;\n\t\tthis.teardown();\n\t\tthis.setState('error');\n\t\tthis.emitter.emit('error', { error });\n\t}\n\n\tprivate setState(state: CallState): void {\n\t\tconst previous = this._state;\n\t\tif (previous === state) return;\n\t\tthis._state = state;\n\t\tthis.emitter.emit('statechange', { state, previous });\n\t}\n\n\tprivate teardown(): void {\n\t\tthis.events?.close();\n\t\tthis.events = null;\n\n\t\tif (this.healthTimer) {\n\t\t\tclearTimeout(this.healthTimer);\n\t\t\tthis.healthTimer = null;\n\t\t}\n\t\tthis.pipelineConfirmed = false;\n\n\t\tif (this.durationTimer) {\n\t\t\tclearInterval(this.durationTimer);\n\t\t\tthis.durationTimer = null;\n\t\t}\n\n\t\tthis.dataChannel?.close();\n\t\tthis.dataChannel = null;\n\n\t\tif (this.localStream) {\n\t\t\tfor (const track of this.localStream.getTracks()) track.stop();\n\t\t\tthis.localStream = null;\n\t\t}\n\n\t\tif (this.remoteAudio) {\n\t\t\tthis.remoteAudio.pause();\n\t\t\tthis.remoteAudio.srcObject = null;\n\t\t\tif (this.ownsAudioElement) this.remoteAudio = null;\n\t\t}\n\n\t\tif (this.pc) {\n\t\t\tthis.pc.ontrack = null;\n\t\t\tthis.pc.onconnectionstatechange = null;\n\t\t\tthis.pc.close();\n\t\t\tthis.pc = null;\n\t\t}\n\t}\n}\n\nexport type { CallEndReason };\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/data-channel.ts","../src/emitter.ts","../src/events.ts","../src/types.ts","../src/turnstile.ts","../src/signaling.ts","../src/call.ts"],"sourcesContent":["export { TalkifCall } from './call.js';\nexport { CallEventsClient } from './events.js';\nexport { PublicSession, SignalingClient, SignalingError } from './signaling.js';\nexport { TurnstileManager } from './turnstile.js';\nexport { isPublicConfig, TalkifCallError, TERMINAL_CALL_STATUSES } from './types.js';\nexport type {\n\tAppMessage,\n\tAuthProvider,\n\tCallEndReason,\n\tCallEventEnvelope,\n\tCallEventType,\n\tCallState,\n\tCallStatus,\n\tCreateCallRequest,\n\tCreateCallResponse,\n\tCreateTestCallRequest,\n\tIceServer,\n\tIceServersResponse,\n\tOfferRequest,\n\tOfferResponse,\n\tPeerLeftMessage,\n\tPublicSessionResponse,\n\tRenegotiateMessage,\n\tSignallingEnvelope,\n\tSignallingPayload,\n\tStartCallOptions,\n\tTalkifCallErrorCode,\n\tTalkifCallEventMap,\n\tTalkifClientConfig,\n\tTalkifConfig,\n\tTalkifPublicClientConfig,\n\tTrackStatusMessage,\n\tTtsChunkEvent,\n\tTtsWordEvent,\n\tWebRTCCall,\n} from './types.js';\n","import type { AppMessage, SignallingPayload, TrackStatusMessage } from './types.js';\n\nconst KEEPALIVE_INTERVAL_MS = 2_000;\n\nexport interface DataChannelHandlers {\n\tonSignalling: (message: SignallingPayload) => void;\n\tonAppMessage: (message: AppMessage) => void;\n}\n\n/**\n * Client side of the pipecat SmallWebRTC data-channel protocol:\n *\n * - `\"ping\"` strings every 2s keep the bot's liveness check happy (the bot\n * treats a connection as active when the last ping is <3s old, with the\n * ICE connection state as fallback).\n * - Inbound JSON is either `{type: \"signalling\", message: {...}}` (protocol)\n * or an arbitrary app message (forwarded to the consumer).\n */\nexport class DataChannel {\n\tprivate channel: RTCDataChannel;\n\tprivate keepalive: ReturnType<typeof setInterval> | null = null;\n\tprivate readonly handlers: DataChannelHandlers;\n\n\tconstructor(pc: RTCPeerConnection, handlers: DataChannelHandlers) {\n\t\tthis.handlers = handlers;\n\t\tthis.channel = pc.createDataChannel('pipecat', { ordered: true });\n\t\tthis.channel.onopen = () => this.startKeepalive();\n\t\tthis.channel.onclose = () => this.stopKeepalive();\n\t\tthis.channel.onmessage = (event) => this.handleMessage(event.data);\n\t}\n\n\tprivate startKeepalive(): void {\n\t\tthis.stopKeepalive();\n\t\tthis.keepalive = setInterval(() => {\n\t\t\tif (this.channel.readyState === 'open') {\n\t\t\t\tthis.channel.send('ping');\n\t\t\t}\n\t\t}, KEEPALIVE_INTERVAL_MS);\n\t}\n\n\tprivate stopKeepalive(): void {\n\t\tif (this.keepalive) {\n\t\t\tclearInterval(this.keepalive);\n\t\t\tthis.keepalive = null;\n\t\t}\n\t}\n\n\tprivate handleMessage(data: unknown): void {\n\t\tif (typeof data !== 'string') return;\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(data);\n\t\t} catch {\n\t\t\treturn; // Non-JSON payloads (e.g. bot-side pings) carry no protocol meaning here.\n\t\t}\n\t\tif (typeof parsed !== 'object' || parsed === null) return;\n\n\t\tconst record = parsed as Record<string, unknown>;\n\t\tif (record['type'] === 'signalling' && typeof record['message'] === 'object' && record['message'] !== null) {\n\t\t\tthis.handlers.onSignalling(record['message'] as SignallingPayload);\n\t\t\treturn;\n\t\t}\n\t\tthis.handlers.onAppMessage(record as AppMessage);\n\t}\n\n\t/** Send an arbitrary JSON app message to the bot pipeline. */\n\tsendAppMessage(message: AppMessage): boolean {\n\t\tif (this.channel.readyState !== 'open') return false;\n\t\tthis.channel.send(JSON.stringify(message));\n\t\treturn true;\n\t}\n\n\t/** Tell the bot to enable/disable one of our media receivers. */\n\tsendTrackStatus(message: Omit<TrackStatusMessage, 'type'>): boolean {\n\t\tif (this.channel.readyState !== 'open') return false;\n\t\tthis.channel.send(JSON.stringify({ type: 'signalling', message: { type: 'trackStatus', ...message } }));\n\t\treturn true;\n\t}\n\n\tclose(): void {\n\t\tthis.stopKeepalive();\n\t\tthis.channel.onopen = null;\n\t\tthis.channel.onclose = null;\n\t\tthis.channel.onmessage = null;\n\t\tif (this.channel.readyState === 'open' || this.channel.readyState === 'connecting') {\n\t\t\tthis.channel.close();\n\t\t}\n\t}\n}\n","/** Minimal typed event emitter — no dependency, browser-safe. */\nexport class Emitter<Events extends object> {\n\tprivate listeners = new Map<keyof Events, Set<(payload: never) => void>>();\n\n\ton<K extends keyof Events>(event: K, listener: (payload: Events[K]) => void): () => void {\n\t\tlet set = this.listeners.get(event);\n\t\tif (!set) {\n\t\t\tset = new Set();\n\t\t\tthis.listeners.set(event, set);\n\t\t}\n\t\tset.add(listener as (payload: never) => void);\n\t\treturn () => this.off(event, listener);\n\t}\n\n\toff<K extends keyof Events>(event: K, listener: (payload: Events[K]) => void): void {\n\t\tthis.listeners.get(event)?.delete(listener as (payload: never) => void);\n\t}\n\n\temit<K extends keyof Events>(event: K, payload: Events[K]): void {\n\t\tconst set = this.listeners.get(event);\n\t\tif (!set) return;\n\t\tfor (const listener of [...set]) {\n\t\t\t(listener as (payload: Events[K]) => void)(payload);\n\t\t}\n\t}\n\n\tremoveAll(): void {\n\t\tthis.listeners.clear();\n\t}\n}\n","import type { CallEventEnvelope } from './types.js';\n\n/** Reconnect backoff schedule (ms). Caps at the last entry. */\nconst BACKOFF_MS = [500, 1_000, 2_000, 4_000, 8_000];\n\nexport interface CallEventsOptions {\n\t/** API origin, e.g. `https://api.talkif.ai`. */\n\tbaseUrl: string;\n\t/** Fresh session token per (re)connect — first-message auth. */\n\tgetToken: () => Promise<string>;\n\t/** Every delivered envelope for the session's call. */\n\tonEvent: (event: CallEventEnvelope) => void;\n\t/**\n\t * A `seq` gap was observed: this connection shed frames under backpressure.\n\t * The client auto-requests a server-side replay on reconnect; consumers that\n\t * render transcripts incrementally may want to clear-and-rebuild on replay.\n\t */\n\tonGap?: (expected: number, received: number) => void;\n\t/** Optional WebSocket constructor override (tests, Node). */\n\twebSocket?: typeof WebSocket;\n}\n\n/**\n * Client for the public realtime events WebSocket\n * (`/api/v1/public/ws/events`).\n *\n * Protocol: connect → send `{\"type\":\"auth\",\"token\":...}` within 5s → send\n * `{\"type\":\"subscribe\"}` (the session is auto-bound to its one call) →\n * receive `{v, type, call_id?, seq, ts, data}` envelopes. Reconnects with\n * backoff; after the first successful connection, reconnect subscribes with\n * `replay: true` to recover anything missed during the gap.\n */\nexport class CallEventsClient {\n\tprivate readonly options: CallEventsOptions;\n\tprivate ws: WebSocket | null = null;\n\tprivate closed = false;\n\tprivate attempts = 0;\n\tprivate lastSeq = 0;\n\tprivate reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n\tconstructor(options: CallEventsOptions) {\n\t\tthis.options = options;\n\t}\n\n\tconnect(): void {\n\t\tif (this.closed || this.ws) return;\n\t\tvoid this.open();\n\t}\n\n\tclose(): void {\n\t\tthis.closed = true;\n\t\tif (this.reconnectTimer) {\n\t\t\tclearTimeout(this.reconnectTimer);\n\t\t\tthis.reconnectTimer = null;\n\t\t}\n\t\tthis.ws?.close(1000);\n\t\tthis.ws = null;\n\t}\n\n\tprivate wsUrl(): string {\n\t\tconst base = this.options.baseUrl.replace(/\\/$/, '').replace(/^http/, 'ws');\n\t\treturn `${base}/api/v1/public/ws/events`;\n\t}\n\n\tprivate async open(): Promise<void> {\n\t\tlet token: string;\n\t\ttry {\n\t\t\ttoken = await this.options.getToken();\n\t\t} catch {\n\t\t\tthis.scheduleReconnect();\n\t\t\treturn;\n\t\t}\n\t\tif (this.closed) return;\n\n\t\tconst WS = this.options.webSocket ?? WebSocket;\n\t\tconst ws = new WS(this.wsUrl());\n\t\tthis.ws = ws;\n\n\t\tws.onopen = () => {\n\t\t\tws.send(JSON.stringify({ type: 'auth', token }));\n\t\t\t// Replay recovers anything emitted before this subscribe landed\n\t\t\t// (the opening word) and anything shed during a reconnect gap.\n\t\t\tws.send(JSON.stringify({ type: 'subscribe', replay: true }));\n\t\t};\n\n\t\tws.onmessage = (message: MessageEvent) => {\n\t\t\tif (typeof message.data !== 'string') return;\n\t\t\tlet envelope: CallEventEnvelope;\n\t\t\ttry {\n\t\t\t\tenvelope = JSON.parse(message.data) as CallEventEnvelope;\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (typeof envelope.type !== 'string' || typeof envelope.seq !== 'number') return;\n\n\t\t\tif (envelope.type === 'subscribed') {\n\t\t\t\tthis.attempts = 0;\n\t\t\t\tthis.lastSeq = envelope.seq;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (envelope.type === 'pong' || envelope.type === 'unsubscribed') {\n\t\t\t\tthis.lastSeq = envelope.seq;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this.lastSeq > 0 && envelope.seq > this.lastSeq + 1) {\n\t\t\t\tthis.options.onGap?.(this.lastSeq + 1, envelope.seq);\n\t\t\t}\n\t\t\tthis.lastSeq = envelope.seq;\n\t\t\tthis.options.onEvent(envelope);\n\t\t};\n\n\t\tws.onclose = (event: CloseEvent) => {\n\t\t\tif (this.ws !== ws) return;\n\t\t\tthis.ws = null;\n\t\t\t// seq is per-connection — reset the tracker.\n\t\t\tthis.lastSeq = 0;\n\t\t\t// 4401/4403 are auth/scope rejections: retrying with the same session\n\t\t\t// can still succeed after a token refresh (getToken re-exchanges), but\n\t\t\t// a scope violation (4403) will never heal — stop there.\n\t\t\tif (event.code === 4403) {\n\t\t\t\tthis.closed = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.scheduleReconnect();\n\t\t};\n\n\t\tws.onerror = () => {\n\t\t\t// onclose follows and handles reconnection.\n\t\t};\n\t}\n\n\tprivate scheduleReconnect(): void {\n\t\tif (this.closed || this.reconnectTimer) return;\n\t\tconst delay = BACKOFF_MS[Math.min(this.attempts, BACKOFF_MS.length - 1)] ?? 8_000;\n\t\tthis.attempts += 1;\n\t\tthis.reconnectTimer = setTimeout(() => {\n\t\t\tthis.reconnectTimer = null;\n\t\t\tvoid this.open();\n\t\t}, delay);\n\t}\n}\n","/**\n * Wire types for the Talkif WebRTC signaling API and the pipecat SmallWebRTC\n * data-channel protocol spoken by Talkif voice bots.\n *\n * These shapes are the public contract. Field casing matches the backend JSON\n * exactly (camelCase over HTTP).\n */\n\n// ---------------------------------------------------------------------------\n// HTTP signaling\n// ---------------------------------------------------------------------------\n\nexport interface IceServer {\n\turls: string;\n\tusername?: string;\n\tcredential?: string;\n}\n\nexport interface IceServersResponse {\n\ticeServers: IceServer[];\n\t/** Seconds remaining on the underlying TURN credential. */\n\tttl: number;\n}\n\nexport interface CreateCallRequest {\n\tflowId: string;\n\tmetadata?: Record<string, unknown>;\n}\n\nexport interface CreateTestCallRequest {\n\tflowId: string;\n\t/**\n\t * Draft flow definition (frontend `FlowDefinition` shape). The library does\n\t * not interpret it — it is passed through to the backend, which validates\n\t * and compiles it for the bot.\n\t */\n\tdefinition: Record<string, unknown>;\n\tmetadata?: Record<string, unknown>;\n}\n\nexport type CallStatus =\n\t| 'CREATED'\n\t| 'BOT_ASSIGNED'\n\t| 'QUEUED'\n\t| 'RINGING'\n\t| 'IN_PROGRESS'\n\t| 'COMPLETED'\n\t| 'FAILED'\n\t| 'NO_ANSWER'\n\t| 'BUSY'\n\t| 'CANCELED';\n\n/** REST statuses that mean the call is genuinely over. */\nexport const TERMINAL_CALL_STATUSES: ReadonlySet<CallStatus> = new Set([\n\t'COMPLETED',\n\t'FAILED',\n\t'NO_ANSWER',\n\t'BUSY',\n\t'CANCELED',\n]);\n\nexport interface CreateCallResponse {\n\tcallId: string;\n\tflowId: string;\n\tstatus: CallStatus;\n\tbotId?: string | null;\n}\n\nexport interface WebRTCCall {\n\tcallId: string;\n\tstatus: CallStatus;\n}\n\nexport interface OfferRequest {\n\tsdp: string;\n\ticeServers?: IceServer[];\n}\n\n/** Public surface: token-exchange response (`POST /public/calls/session`). */\nexport interface PublicSessionResponse {\n\t/** Signed session token — sent as `Authorization: Bearer <token>`. */\n\ttoken: string;\n\t/** Seconds until the token expires (~15 min). */\n\texpiresIn: number;\n\t/** The flow this session may call (bound to the publishable key). */\n\tflowId: string;\n}\n\nexport interface OfferResponse {\n\tsdp: string;\n\tsdpType: string;\n\tbotId?: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Data-channel protocol (pipecat SmallWebRTC — bot side)\n// ---------------------------------------------------------------------------\n\n/** Bot → client: bot requests a new offer/answer cycle. */\nexport interface RenegotiateMessage {\n\ttype: 'renegotiate';\n}\n\n/** Bot → client: bot is closing the connection. */\nexport interface PeerLeftMessage {\n\ttype: 'peerLeft';\n}\n\n/** Client → bot: enable/disable a media receiver. 0=audio, 1=video, 2=screen. */\nexport interface TrackStatusMessage {\n\ttype: 'trackStatus';\n\treceiver_index: 0 | 1 | 2;\n\tenabled: boolean;\n}\n\nexport type SignallingPayload = RenegotiateMessage | PeerLeftMessage | TrackStatusMessage;\n\nexport interface SignallingEnvelope {\n\ttype: 'signalling';\n\tmessage: SignallingPayload;\n}\n\n/**\n * Anything on the data channel that is not the signalling envelope (and not a\n * `\"ping\"` keepalive string) is an app message: arbitrary JSON exchanged with\n * the bot pipeline.\n */\nexport type AppMessage = Record<string, unknown>;\n\n// ---------------------------------------------------------------------------\n// Realtime events WebSocket (`/api/v1/public/ws/events`)\n// ---------------------------------------------------------------------------\n\n/**\n * Server envelope on the realtime events WebSocket. `seq` is per-connection\n * and monotonic — a gap means THIS connection dropped frames under\n * backpressure, not that the call missed anything.\n */\nexport interface CallEventEnvelope {\n\tv: number;\n\ttype: string;\n\tcall_id?: string;\n\tseq: number;\n\tts: number;\n\tdata: Record<string, unknown>;\n}\n\n/** Event types the public WS delivers for the session's call. */\nexport type CallEventType =\n\t| 'transcript'\n\t| 'interim'\n\t| 'status'\n\t| 'tts'\n\t| 'tts_word'\n\t| 'speech'\n\t| 'turn'\n\t| 'node_transition'\n\t| 'error';\n\n// ---------------------------------------------------------------------------\n// Client state machine & events\n// ---------------------------------------------------------------------------\n\nexport type CallState =\n\t| 'idle'\n\t| 'requesting'\n\t| 'connecting'\n\t| 'connected'\n\t| 'ended'\n\t| 'error';\n\nexport interface TalkifCallEventMap {\n\t/** State machine transition. */\n\tstatechange: { state: CallState; previous: CallState };\n\t/** Media is flowing and the SDP answer is applied. */\n\tconnected: { callId: string; botId: string | null };\n\t/** Remote (bot) audio track arrived. */\n\ttrack: { stream: MediaStream };\n\t/** Call finished normally (user hangup, bot peerLeft, terminal status). */\n\tended: { reason: CallEndReason };\n\t/** Fatal failure. The call is torn down; state is 'error'. */\n\terror: { error: TalkifCallError };\n\t/** One-second duration tick while connected. */\n\ttick: { durationSecs: number };\n\t/** JSON app message from the bot over the data channel. */\n\tappmessage: { message: AppMessage };\n\t/** Bot asked for renegotiation. Emitted after the re-offer is attempted. */\n\trenegotiated: { ok: boolean };\n\t/** Mute toggled. */\n\tmutechange: { muted: boolean };\n\t/**\n\t * Any realtime event for this call from the events WebSocket (public mode).\n\t * Fires for every delivered envelope, including those with dedicated\n\t * events below.\n\t */\n\tcallevent: { event: CallEventEnvelope };\n\t/** Final transcript line (public mode, via the events WebSocket). */\n\ttranscript: { role: string; content: string; data: Record<string, unknown> };\n\t/** Interim (in-progress) transcription (public mode). May be dropped under backpressure. */\n\tinterim: { data: Record<string, unknown> };\n\t/**\n\t * Word-level TTS timing (public mode). Drives karaoke-style caption\n\t * highlighting; only words actually spoken are delivered. May be dropped\n\t * under backpressure.\n\t */\n\tttsword: TtsWordEvent;\n\t/**\n\t * Sentence-level chunk of the agent's reply (public mode), emitted when the\n\t * LLM output is handed to TTS synthesis — ahead of audio playback. Use for\n\t * chat-style streaming of the reply; reconcile with the final `transcript`\n\t * (on interruption the tail may never be spoken). May be dropped under\n\t * backpressure.\n\t */\n\tttschunk: TtsChunkEvent;\n}\n\n/** Payload of a `tts` realtime event. */\nexport interface TtsChunkEvent {\n\t/** Sentence-level text chunk of the agent's reply. */\n\ttext: string;\n\t/** Unix timestamp from the bot (seconds, fractional). */\n\ttimestamp: number;\n\t/** Full raw event payload (character counts, TTS timing metrics…). */\n\tdata: Record<string, unknown>;\n}\n\n/** Payload of a `tts_word` realtime event. */\nexport interface TtsWordEvent {\n\t/** The spoken word. */\n\tword: string;\n\t/** Presentation timestamp — when the word is spoken in the audio (ms), if known. */\n\tptsMs: number | null;\n\t/** Unix timestamp from the bot (seconds, fractional). */\n\ttimestamp: number;\n\t/** Full raw event payload. */\n\tdata: Record<string, unknown>;\n}\n\nexport type CallEndReason =\n\t| 'local-hangup'\n\t| 'peer-left'\n\t| 'terminal-status'\n\t| 'external';\n\nexport type TalkifCallErrorCode =\n\t| 'media-unavailable'\n\t| 'signaling-failed'\n\t| 'connection-failed'\n\t| 'pipeline-dead'\n\t| 'renegotiation-failed'\n\t/** Publishable key rejected, origin not allowed, or session expired. */\n\t| 'session-rejected'\n\t/** This session already has an active call (multi-tab). */\n\t| 'call-active'\n\t/** Rate/concurrency limit hit — retry later. */\n\t| 'rate-limited'\n\t/** No bot available right now — retry shortly. */\n\t| 'no-agent';\n\nexport class TalkifCallError extends Error {\n\treadonly code: TalkifCallErrorCode;\n\toverride readonly cause?: unknown;\n\n\tconstructor(code: TalkifCallErrorCode, message: string, cause?: unknown) {\n\t\tsuper(message);\n\t\tthis.name = 'TalkifCallError';\n\t\tthis.code = code;\n\t\tthis.cause = cause;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Configuration\n// ---------------------------------------------------------------------------\n\n/**\n * Supplies the Authorization header value for signaling requests.\n * Called per request so short-lived tokens (JWT refresh, widget session\n * tokens) stay current. Return e.g. `\"Bearer <token>\"`.\n */\nexport type AuthProvider = () => string | Promise<string>;\n\nexport interface TalkifClientConfig {\n\t/** API origin, e.g. `https://api.talkif.ai`. No trailing slash. */\n\tbaseUrl: string;\n\taccountId: string;\n\tauth: AuthProvider;\n\t/** Override fetch (tests, SSR polyfills). Defaults to global fetch. */\n\tfetch?: typeof fetch;\n}\n\n/**\n * Public (embeddable) configuration: the browser-safe publishable key from the\n * flow's public-access settings. The library exchanges it for a short-lived\n * session token itself — no bearer token, no account id. The page's Origin\n * must be on the key's allowlist.\n */\nexport interface TalkifPublicClientConfig {\n\t/** API origin, e.g. `https://api.talkif.ai`. No trailing slash. */\n\tbaseUrl: string;\n\t/** Publishable key (`pk_live_...`) — safe to ship in page source. */\n\tpublishableKey: string;\n\t/**\n\t * Cloudflare Turnstile sitekey (Talkif's — public, safe to ship). When set,\n\t * the library renders an invisible Turnstile challenge and produces a token\n\t * for each session exchange automatically. Leave unset to disable the gate\n\t * (dev/local, or before Turnstile is provisioned).\n\t */\n\tturnstileSiteKey?: string;\n\t/**\n\t * Manual Turnstile token supplier. Overrides `turnstileSiteKey` — supply\n\t * this only if you render Turnstile yourself. Called at each exchange.\n\t */\n\tturnstileToken?: () => string | Promise<string>;\n\t/** Override fetch (tests, SSR polyfills). Defaults to global fetch. */\n\tfetch?: typeof fetch;\n}\n\nexport type TalkifConfig = TalkifClientConfig | TalkifPublicClientConfig;\n\nexport function isPublicConfig(config: TalkifConfig): config is TalkifPublicClientConfig {\n\treturn 'publishableKey' in config;\n}\n\nexport interface StartCallOptions {\n\t/**\n\t * Start a call against a published flow. Required in authenticated mode.\n\t * Ignored in public mode — the flow is bound to the publishable key.\n\t */\n\tflowId?: string;\n\t/**\n\t * When set, uses the draft test-call endpoint with this definition instead\n\t * of the published flow.\n\t */\n\tdraftDefinition?: Record<string, unknown>;\n\tmetadata?: Record<string, unknown>;\n\t/** Constraints for getUserMedia. Defaults to `{ audio: true }`. */\n\taudio?: MediaTrackConstraints | boolean;\n\t/**\n\t * Attach remote audio to this element instead of an internally created\n\t * autoplay `Audio()`. Pass `null` to disable playback entirely (consumer\n\t * handles the `track` event itself).\n\t */\n\taudioElement?: HTMLAudioElement | null;\n}\n","/**\n * Cloudflare Turnstile integration — the \"prove you're a real browser\" gate\n * that stops scripted abuse of the public call surface.\n *\n * The customer does nothing: the widget self-injects Cloudflare's script,\n * renders an invisible Turnstile widget with Talkif's sitekey, and produces a\n * fresh token for each session exchange. The sitekey is public and safe to\n * ship; the matching secret lives only in the backend.\n */\n\nconst TURNSTILE_SCRIPT_URL =\n\t'https://challenges.cloudflare.com/turnstile/v0/api.js';\n\n/** Minimal shape of the global `turnstile` object we use. */\ninterface TurnstileApi {\n\trender(\n\t\tcontainer: HTMLElement,\n\t\toptions: {\n\t\t\tsitekey: string;\n\t\t\tcallback?: (token: string) => void;\n\t\t\t'error-callback'?: (code?: string) => void;\n\t\t\t'expired-callback'?: () => void;\n\t\t\tsize?: 'invisible' | 'normal' | 'compact' | 'flexible';\n\t\t\texecution?: 'render' | 'execute';\n\t\t\tretry?: 'auto' | 'never';\n\t\t}\n\t): string;\n\texecute(widgetId: string, options?: { sitekey?: string }): void;\n\treset(widgetId: string): void;\n\tremove(widgetId: string): void;\n}\n\ndeclare global {\n\tinterface Window {\n\t\tturnstile?: TurnstileApi;\n\t}\n}\n\nlet scriptPromise: Promise<void> | null = null;\n\n/** Load Cloudflare's Turnstile script once, shared across all instances. */\nfunction loadScript(): Promise<void> {\n\tif (typeof document === 'undefined') {\n\t\treturn Promise.reject(new Error('Turnstile requires a browser environment'));\n\t}\n\tif (window.turnstile) return Promise.resolve();\n\tscriptPromise ??= new Promise<void>((resolve, reject) => {\n\t\tconst existing = document.querySelector<HTMLScriptElement>(\n\t\t\t`script[src=\"${TURNSTILE_SCRIPT_URL}\"]`\n\t\t);\n\t\tif (existing) {\n\t\t\texisting.addEventListener('load', () => resolve());\n\t\t\texisting.addEventListener('error', () => reject(new Error('Turnstile script failed to load')));\n\t\t\tif (window.turnstile) resolve();\n\t\t\treturn;\n\t\t}\n\t\tconst script = document.createElement('script');\n\t\tscript.src = TURNSTILE_SCRIPT_URL;\n\t\tscript.async = true;\n\t\tscript.defer = true;\n\t\tscript.onload = () => resolve();\n\t\tscript.onerror = () => reject(new Error('Turnstile script failed to load'));\n\t\tdocument.head.appendChild(script);\n\t});\n\treturn scriptPromise;\n}\n\n/**\n * Manages one invisible Turnstile widget and hands out fresh tokens.\n *\n * Turnstile tokens are single-use and short-lived, so we `execute()` a fresh\n * challenge for each call to `getToken()` (i.e. each session exchange). The\n * widget is rendered lazily on first use and torn down with `dispose()`.\n */\nexport class TurnstileManager {\n\tprivate readonly siteKey: string;\n\tprivate container: HTMLDivElement | null = null;\n\tprivate widgetId: string | null = null;\n\tprivate pending: {\n\t\tresolve: (token: string) => void;\n\t\treject: (err: Error) => void;\n\t} | null = null;\n\n\tconstructor(siteKey: string) {\n\t\tthis.siteKey = siteKey;\n\t}\n\n\t/** Obtain a fresh Turnstile token. Rejects if the challenge fails. */\n\tasync getToken(): Promise<string> {\n\t\tawait loadScript();\n\t\tconst turnstile = window.turnstile;\n\t\tif (!turnstile) throw new Error('Turnstile unavailable after script load');\n\n\t\tif (!this.container) {\n\t\t\t// A hidden, off-screen host — the invisible widget never shows UI\n\t\t\t// but Cloudflare still requires an element to render into.\n\t\t\tthis.container = document.createElement('div');\n\t\t\tthis.container.style.position = 'fixed';\n\t\t\tthis.container.style.width = '0';\n\t\t\tthis.container.style.height = '0';\n\t\t\tthis.container.style.overflow = 'hidden';\n\t\t\tthis.container.style.pointerEvents = 'none';\n\t\t\tthis.container.setAttribute('aria-hidden', 'true');\n\t\t\tdocument.body.appendChild(this.container);\n\t\t}\n\n\t\tif (this.widgetId === null) {\n\t\t\tthis.widgetId = turnstile.render(this.container, {\n\t\t\t\tsitekey: this.siteKey,\n\t\t\t\tsize: 'invisible',\n\t\t\t\texecution: 'execute',\n\t\t\t\tretry: 'auto',\n\t\t\t\tcallback: (token: string) => {\n\t\t\t\t\tthis.pending?.resolve(token);\n\t\t\t\t\tthis.pending = null;\n\t\t\t\t},\n\t\t\t\t'error-callback': (code?: string) => {\n\t\t\t\t\tthis.pending?.reject(new Error(`Turnstile challenge failed${code ? `: ${code}` : ''}`));\n\t\t\t\t\tthis.pending = null;\n\t\t\t\t},\n\t\t\t\t'expired-callback': () => {\n\t\t\t\t\t// Token expired before use — the next getToken() re-executes.\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\treturn new Promise<string>((resolve, reject) => {\n\t\t\t// Only one outstanding challenge at a time; supersede any prior.\n\t\t\tif (this.pending) {\n\t\t\t\tthis.pending.reject(new Error('Turnstile challenge superseded'));\n\t\t\t}\n\t\t\tthis.pending = { resolve, reject };\n\t\t\tconst id = this.widgetId;\n\t\t\tif (id === null) {\n\t\t\t\treject(new Error('Turnstile widget not rendered'));\n\t\t\t\tthis.pending = null;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tturnstile.reset(id);\n\t\t\t\tturnstile.execute(id, { sitekey: this.siteKey });\n\t\t\t} catch (err) {\n\t\t\t\tthis.pending = null;\n\t\t\t\treject(err instanceof Error ? err : new Error('Turnstile execute failed'));\n\t\t\t}\n\t\t});\n\t}\n\n\tdispose(): void {\n\t\tif (this.widgetId !== null && window.turnstile) {\n\t\t\ttry {\n\t\t\t\twindow.turnstile.remove(this.widgetId);\n\t\t\t} catch {\n\t\t\t\t// best-effort\n\t\t\t}\n\t\t}\n\t\tthis.widgetId = null;\n\t\tthis.pending = null;\n\t\tif (this.container) {\n\t\t\tthis.container.remove();\n\t\t\tthis.container = null;\n\t\t}\n\t}\n}\n","import type {\n\tCreateCallRequest,\n\tCreateCallResponse,\n\tCreateTestCallRequest,\n\tIceServersResponse,\n\tOfferRequest,\n\tOfferResponse,\n\tPublicSessionResponse,\n\tTalkifConfig,\n\tTalkifPublicClientConfig,\n\tWebRTCCall,\n} from './types.js';\nimport { isPublicConfig, TalkifCallError } from './types.js';\nimport { TurnstileManager } from './turnstile.js';\n\nexport class SignalingError extends Error {\n\treadonly status: number;\n\treadonly body: unknown;\n\n\tconstructor(status: number, message: string, body: unknown) {\n\t\tsuper(message);\n\t\tthis.name = 'SignalingError';\n\t\tthis.status = status;\n\t\tthis.body = body;\n\t}\n}\n\n/** Re-exchange the publishable key this many ms before the token expires. */\nconst SESSION_REFRESH_MARGIN_MS = 60_000;\n\n/**\n * Holds the short-lived session token for the public surface: exchanges the\n * publishable key on first use, re-exchanges near expiry, and can be\n * invalidated on a 401 so the next request retries with a fresh token.\n */\nexport class PublicSession {\n\tprivate readonly config: TalkifPublicClientConfig;\n\tprivate token: string | null = null;\n\tprivate expiresAt = 0;\n\tprivate flowIdValue: string | null = null;\n\tprivate inflight: Promise<string> | null = null;\n\tprivate turnstile: TurnstileManager | null = null;\n\n\tconstructor(config: TalkifPublicClientConfig) {\n\t\tthis.config = config;\n\t\t// Built-in Turnstile: render an invisible challenge from the sitekey,\n\t\t// unless the caller supplies their own token provider.\n\t\tif (config.turnstileSiteKey && !config.turnstileToken) {\n\t\t\tthis.turnstile = new TurnstileManager(config.turnstileSiteKey);\n\t\t}\n\t}\n\n\t/** A fresh Turnstile token, from the manual supplier or the built-in\n\t * manager; undefined when no gate is configured (dev/local). */\n\tprivate async turnstileToken(): Promise<string | undefined> {\n\t\tif (this.config.turnstileToken) {\n\t\t\treturn this.config.turnstileToken();\n\t\t}\n\t\tif (this.turnstile) {\n\t\t\treturn this.turnstile.getToken();\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/** Tear down the Turnstile widget (call when the client is disposed). */\n\tdispose(): void {\n\t\tthis.turnstile?.dispose();\n\t\tthis.turnstile = null;\n\t}\n\n\t/** Flow bound to the publishable key (known after the first exchange). */\n\tget flowId(): string | null {\n\t\treturn this.flowIdValue;\n\t}\n\n\tasync getToken(): Promise<string> {\n\t\tif (this.token && Date.now() < this.expiresAt - SESSION_REFRESH_MARGIN_MS) {\n\t\t\treturn this.token;\n\t\t}\n\t\t// Single-flight the exchange: concurrent requests share one round-trip.\n\t\tthis.inflight ??= this.exchange().finally(() => {\n\t\t\tthis.inflight = null;\n\t\t});\n\t\treturn this.inflight;\n\t}\n\n\tinvalidate(): void {\n\t\tthis.token = null;\n\t\tthis.expiresAt = 0;\n\t}\n\n\tprivate async exchange(): Promise<string> {\n\t\tconst doFetch = this.config.fetch ?? fetch;\n\t\tconst turnstileToken = await this.turnstileToken();\n\t\tconst response = await doFetch(\n\t\t\t`${this.config.baseUrl.replace(/\\/$/, '')}/api/v1/public/calls/session`,\n\t\t\t{\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tpublishableKey: this.config.publishableKey,\n\t\t\t\t\t...(turnstileToken !== undefined ? { turnstileToken } : {}),\n\t\t\t\t}),\n\t\t\t}\n\t\t);\n\t\tconst parsed: unknown = await response.json().catch(() => null);\n\t\tif (!response.ok) {\n\t\t\tthrow new SignalingError(\n\t\t\t\tresponse.status,\n\t\t\t\t'Session exchange failed — check the publishable key and that this origin is on its allowlist',\n\t\t\t\tparsed\n\t\t\t);\n\t\t}\n\t\tconst session = parsed as PublicSessionResponse;\n\t\tthis.token = session.token;\n\t\tthis.expiresAt = Date.now() + session.expiresIn * 1_000;\n\t\tthis.flowIdValue = session.flowId;\n\t\treturn session.token;\n\t}\n}\n\n/**\n * Thin HTTP client for the Talkif WebRTC signaling routes. Two modes:\n * - authenticated (dashboard/API): `/api/v1/accounts/{accountId}/calls/webrtc`\n * - public (embed): `/api/v1/public/calls`, session token minted from the\n * publishable key and refreshed transparently.\n */\nexport class SignalingClient {\n\tprivate readonly config: TalkifConfig;\n\treadonly session: PublicSession | null;\n\n\tconstructor(config: TalkifConfig) {\n\t\tthis.config = config;\n\t\tthis.session = isPublicConfig(config) ? new PublicSession(config) : null;\n\t}\n\n\tget isPublic(): boolean {\n\t\treturn this.session !== null;\n\t}\n\n\tprivate get base(): string {\n\t\tconst baseUrl = this.config.baseUrl.replace(/\\/$/, '');\n\t\tif (isPublicConfig(this.config)) {\n\t\t\treturn `${baseUrl}/api/v1/public/calls`;\n\t\t}\n\t\treturn `${baseUrl}/api/v1/accounts/${this.config.accountId}/calls/webrtc`;\n\t}\n\n\tprivate async authorization(): Promise<string> {\n\t\tif (this.session) {\n\t\t\treturn `Bearer ${await this.session.getToken()}`;\n\t\t}\n\t\treturn (this.config as { auth: () => string | Promise<string> }).auth();\n\t}\n\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST',\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\tretriedAuth = false\n\t): Promise<T> {\n\t\tconst doFetch = this.config.fetch ?? fetch;\n\t\tconst authorization = await this.authorization();\n\t\tconst response = await doFetch(`${this.base}${path}`, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\tauthorization,\n\t\t\t\t...(body !== undefined ? { 'content-type': 'application/json' } : {}),\n\t\t\t},\n\t\t\t...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n\t\t});\n\n\t\t// Public mode: an expired/rotated session token gets one transparent\n\t\t// re-exchange + retry before surfacing the failure.\n\t\tif (response.status === 401 && this.session && !retriedAuth) {\n\t\t\tthis.session.invalidate();\n\t\t\treturn this.request(method, path, body, true);\n\t\t}\n\n\t\tlet parsed: unknown = null;\n\t\tconst text = await response.text();\n\t\tif (text) {\n\t\t\ttry {\n\t\t\t\tparsed = JSON.parse(text);\n\t\t\t} catch {\n\t\t\t\tparsed = text;\n\t\t\t}\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst message =\n\t\t\t\ttypeof parsed === 'object' && parsed !== null && 'detail' in parsed\n\t\t\t\t\t? String((parsed as { detail: unknown }).detail)\n\t\t\t\t\t: typeof parsed === 'object' && parsed !== null && 'message' in parsed\n\t\t\t\t\t\t? String((parsed as { message: unknown }).message)\n\t\t\t\t\t\t: `Signaling request failed: ${method} ${path} → ${response.status}`;\n\t\t\tthrow new SignalingError(response.status, message, parsed);\n\t\t}\n\t\treturn parsed as T;\n\t}\n\n\tcreateCall(body: CreateCallRequest): Promise<CreateCallResponse> {\n\t\tif (this.isPublic) {\n\t\t\t// Flow is bound to the publishable key server-side; body is empty.\n\t\t\treturn this.request('POST', '/calls', {});\n\t\t}\n\t\treturn this.request('POST', '', body);\n\t}\n\n\tcreateTestCall(body: CreateTestCallRequest): Promise<CreateCallResponse> {\n\t\tif (this.isPublic) {\n\t\t\tthrow new TalkifCallError(\n\t\t\t\t'signaling-failed',\n\t\t\t\t'Draft test calls are not available on the public surface'\n\t\t\t);\n\t\t}\n\t\treturn this.request('POST', '/test', body);\n\t}\n\n\tgetIceServers(): Promise<IceServersResponse> {\n\t\treturn this.request('GET', '/ice-servers');\n\t}\n\n\tsendOffer(callId: string, body: OfferRequest): Promise<OfferResponse> {\n\t\tconst path = this.isPublic ? `/calls/${callId}/offer` : `/${callId}/offer`;\n\t\treturn this.request('POST', path, body);\n\t}\n\n\tgetCall(callId: string): Promise<WebRTCCall> {\n\t\tconst path = this.isPublic ? `/calls/${callId}` : `/${callId}`;\n\t\treturn this.request('GET', path);\n\t}\n}\n\n/** Map a signaling failure to the library's typed error. */\nexport function asSignalingCallError(error: unknown): TalkifCallError {\n\tif (error instanceof TalkifCallError) return error;\n\tif (error instanceof SignalingError) {\n\t\tconst code =\n\t\t\terror.status === 401\n\t\t\t\t? 'session-rejected'\n\t\t\t\t: error.status === 409\n\t\t\t\t\t? 'call-active'\n\t\t\t\t\t: error.status === 429\n\t\t\t\t\t\t? 'rate-limited'\n\t\t\t\t\t\t: error.status === 503\n\t\t\t\t\t\t\t? 'no-agent'\n\t\t\t\t\t\t\t: 'signaling-failed';\n\t\treturn new TalkifCallError(code, error.message, error);\n\t}\n\tconst message = error instanceof Error ? error.message : 'Signaling request failed';\n\treturn new TalkifCallError('signaling-failed', message, error);\n}\n","import { DataChannel } from './data-channel.js';\nimport { Emitter } from './emitter.js';\nimport { CallEventsClient } from './events.js';\nimport { asSignalingCallError, SignalingClient } from './signaling.js';\nimport type {\n\tAppMessage,\n\tCallEndReason,\n\tCallEventEnvelope,\n\tCallState,\n\tIceServer,\n\tSignallingPayload,\n\tStartCallOptions,\n\tTalkifCallEventMap,\n\tTalkifConfig,\n} from './types.js';\nimport { TalkifCallError, TERMINAL_CALL_STATUSES } from './types.js';\n\n// If no external health confirmation (SSE/app message) arrives within this\n// window after media connects, verify call health against the backend over\n// REST before tearing down. Silence alone is NOT proof of pipeline death.\nconst PIPELINE_HEALTH_TIMEOUT_MS = 15_000;\n\n// Safety net for ICE gathering: send whatever we have if no relay candidate\n// shows up. With iceTransportPolicy 'relay' the first relay(udp) candidate is\n// all the bot needs; TURN tcp/443 allocations routinely never complete, so\n// waiting for iceGatheringState === 'complete' would stall every call.\nconst ICE_FIRST_RELAY_TIMEOUT_MS = 1_500;\n\n/**\n * One WebRTC voice session with a Talkif agent.\n *\n * Lifecycle: `idle → requesting → connecting → connected → ended | error`.\n * A TalkifCall instance is single-use: create a new one per call.\n */\nexport class TalkifCall {\n\tprivate readonly signaling: SignalingClient;\n\tprivate readonly config: TalkifConfig;\n\tprivate readonly emitter = new Emitter<TalkifCallEventMap>();\n\tprivate events: CallEventsClient | null = null;\n\n\tprivate pc: RTCPeerConnection | null = null;\n\tprivate localStream: MediaStream | null = null;\n\tprivate remoteAudio: HTMLAudioElement | null = null;\n\tprivate ownsAudioElement = false;\n\tprivate dataChannel: DataChannel | null = null;\n\tprivate iceServers: IceServer[] = [];\n\n\tprivate durationTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate healthTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate pipelineConfirmed = false;\n\tprivate renegotiating = false;\n\n\tprivate _state: CallState = 'idle';\n\tprivate _callId: string | null = null;\n\tprivate _botId: string | null = null;\n\tprivate _muted = false;\n\tprivate _durationSecs = 0;\n\n\tconstructor(config: TalkifConfig) {\n\t\tthis.config = config;\n\t\tthis.signaling = new SignalingClient(config);\n\t}\n\n\t// -- public surface -------------------------------------------------------\n\n\tget state(): CallState {\n\t\treturn this._state;\n\t}\n\n\tget callId(): string | null {\n\t\treturn this._callId;\n\t}\n\n\tget botId(): string | null {\n\t\treturn this._botId;\n\t}\n\n\tget muted(): boolean {\n\t\treturn this._muted;\n\t}\n\n\tget durationSecs(): number {\n\t\treturn this._durationSecs;\n\t}\n\n\ton<K extends keyof TalkifCallEventMap>(\n\t\tevent: K,\n\t\tlistener: (payload: TalkifCallEventMap[K]) => void\n\t): () => void {\n\t\treturn this.emitter.on(event, listener);\n\t}\n\n\toff<K extends keyof TalkifCallEventMap>(\n\t\tevent: K,\n\t\tlistener: (payload: TalkifCallEventMap[K]) => void\n\t): void {\n\t\tthis.emitter.off(event, listener);\n\t}\n\n\t/**\n\t * External health signal (e.g. the dashboard's SSE layer saw a\n\t * RUNNING/CONNECTED status). Cancels the REST health watchdog.\n\t */\n\tconfirmPipelineHealthy(): void {\n\t\tthis.pipelineConfirmed = true;\n\t\tif (this.healthTimer) {\n\t\t\tclearTimeout(this.healthTimer);\n\t\t\tthis.healthTimer = null;\n\t\t}\n\t}\n\n\t/**\n\t * External terminal signal (e.g. SSE reported COMPLETED). Tears down and\n\t * transitions to 'ended'.\n\t */\n\tendedExternally(): void {\n\t\tif (this._state !== 'connecting' && this._state !== 'connected') return;\n\t\tthis.teardown();\n\t\tthis.setState('ended');\n\t\tthis.emitter.emit('ended', { reason: 'external' });\n\t}\n\n\t/** Send an arbitrary JSON app message to the bot over the data channel. */\n\tsendAppMessage(message: AppMessage): boolean {\n\t\treturn this.dataChannel?.sendAppMessage(message) ?? false;\n\t}\n\n\tsetMuted(muted: boolean): void {\n\t\tif (!this.localStream) return;\n\t\tfor (const track of this.localStream.getAudioTracks()) {\n\t\t\ttrack.enabled = !muted;\n\t\t}\n\t\tthis._muted = muted;\n\t\tthis.emitter.emit('mutechange', { muted });\n\t}\n\n\t/** User-initiated hangup. */\n\thangup(): void {\n\t\tif (this._state === 'idle' || this._state === 'ended' || this._state === 'error') return;\n\t\tthis.teardown();\n\t\tthis.setState('ended');\n\t\tthis.emitter.emit('ended', { reason: 'local-hangup' });\n\t}\n\n\t/** Full teardown regardless of state — safe to call multiple times. */\n\tdispose(): void {\n\t\tthis.teardown();\n\t\tthis.signaling.session?.dispose();\n\t\tthis.emitter.removeAll();\n\t}\n\n\t// -- lifecycle ------------------------------------------------------------\n\n\tasync start(options: StartCallOptions): Promise<void> {\n\t\tif (this._state !== 'idle') {\n\t\t\tthrow new TalkifCallError('signaling-failed', `Cannot start a call from state '${this._state}' — create a new TalkifCall`);\n\t\t}\n\t\tthis.setState('requesting');\n\n\t\tif (!this.signaling.isPublic && !options.flowId) {\n\t\t\tthrow new TalkifCallError('signaling-failed', 'flowId is required in authenticated mode');\n\t\t}\n\n\t\ttry {\n\t\t\t// Run bot warmup (create call → bot assignment, 2-5s) in parallel with\n\t\t\t// local setup (ICE fetch, mic, offer, candidate gathering).\n\t\t\tconst flowId = options.flowId ?? '';\n\t\t\tconst [callResponse, local] = await Promise.all([\n\t\t\t\toptions.draftDefinition !== undefined\n\t\t\t\t\t? this.signaling.createTestCall({\n\t\t\t\t\t\t\tflowId,\n\t\t\t\t\t\t\tdefinition: options.draftDefinition,\n\t\t\t\t\t\t\t...(options.metadata !== undefined ? { metadata: options.metadata } : {}),\n\t\t\t\t\t\t})\n\t\t\t\t\t: this.signaling.createCall({\n\t\t\t\t\t\t\tflowId,\n\t\t\t\t\t\t\t...(options.metadata !== undefined ? { metadata: options.metadata } : {}),\n\t\t\t\t\t\t}),\n\t\t\t\tthis.prepareLocal(options),\n\t\t\t]);\n\n\t\t\tthis._callId = callResponse.callId;\n\t\t\tthis.pc = local.pc;\n\t\t\tthis.localStream = local.stream;\n\t\t\tthis.setState('connecting');\n\n\t\t\t// Subscribe to events before the media handshake so the WS is\n\t\t\t// listening before the bot speaks (the session→call mapping already\n\t\t\t// exists once create-call returns).\n\t\t\tthis.connectEvents();\n\n\t\t\tthis.pc.ontrack = (event) => {\n\t\t\t\tconst stream = event.streams[0];\n\t\t\t\tif (!stream) return;\n\t\t\t\tthis.attachRemoteAudio(stream, options);\n\t\t\t\tthis.emitter.emit('track', { stream });\n\t\t\t};\n\n\t\t\tthis.pc.onconnectionstatechange = () => {\n\t\t\t\tconst state = this.pc?.connectionState;\n\t\t\t\tif (state === 'failed' || state === 'disconnected') {\n\t\t\t\t\tthis.fail(new TalkifCallError('connection-failed', 'WebRTC connection failed'));\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tthis.dataChannel = new DataChannel(this.pc, {\n\t\t\t\tonSignalling: (message) => this.handleSignalling(message),\n\t\t\t\tonAppMessage: (message) => this.emitter.emit('appmessage', { message }),\n\t\t\t});\n\n\t\t\tconst answer = await this.signaling.sendOffer(callResponse.callId, {\n\t\t\t\tsdp: this.mustLocalSdp(),\n\t\t\t\ticeServers: this.iceServers,\n\t\t\t});\n\t\t\tawait this.pc.setRemoteDescription({ type: 'answer', sdp: answer.sdp });\n\t\t\tthis._botId = answer.botId ?? null;\n\n\t\t\tthis.setState('connected');\n\t\t\tthis.emitter.emit('connected', { callId: callResponse.callId, botId: this._botId });\n\t\t\tthis.startDurationTimer();\n\t\t\tthis.armHealthWatchdog(callResponse.callId);\n\t\t} catch (error) {\n\t\t\tthis.fail(\n\t\t\t\terror instanceof TalkifCallError ? error : asSignalingCallError(error)\n\t\t\t);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate async prepareLocal(options: StartCallOptions): Promise<{ pc: RTCPeerConnection; stream: MediaStream }> {\n\t\tconst { iceServers } = await this.signaling.getIceServers();\n\t\tthis.iceServers = iceServers;\n\n\t\tif (!navigator.mediaDevices?.getUserMedia) {\n\t\t\tthrow new TalkifCallError('media-unavailable', 'Microphone access unavailable. This requires HTTPS or localhost.');\n\t\t}\n\t\tconst stream = await navigator.mediaDevices\n\t\t\t.getUserMedia({ audio: options.audio ?? true })\n\t\t\t.catch((cause: unknown) => {\n\t\t\t\tthrow new TalkifCallError('media-unavailable', 'Microphone permission denied or unavailable', cause);\n\t\t\t});\n\n\t\t// Relay-only transport: skips host/srflx gathering (no dead-interface\n\t\t// timeouts), works through firewalls that block UDP, avoids TURN\n\t\t// \"Forbidden IP\" errors for private IPs, and gathers in ~100-200ms.\n\t\tconst pc = new RTCPeerConnection({\n\t\t\ticeServers: iceServers.map((s) => ({\n\t\t\t\turls: s.urls,\n\t\t\t\t...(s.username !== undefined ? { username: s.username } : {}),\n\t\t\t\t...(s.credential !== undefined ? { credential: s.credential } : {}),\n\t\t\t})),\n\t\t\ticeTransportPolicy: 'relay',\n\t\t});\n\t\tfor (const track of stream.getTracks()) {\n\t\t\tpc.addTrack(track, stream);\n\t\t}\n\n\t\tconst offer = await pc.createOffer();\n\t\tawait pc.setLocalDescription(offer);\n\t\tif (!offer.sdp) {\n\t\t\tthrow new TalkifCallError('signaling-failed', 'Failed to create SDP offer');\n\t\t}\n\n\t\tawait this.waitForFirstRelayCandidate(pc);\n\t\treturn { pc, stream };\n\t}\n\n\tprivate waitForFirstRelayCandidate(pc: RTCPeerConnection): Promise<void> {\n\t\treturn new Promise<void>((resolve) => {\n\t\t\tif (pc.iceGatheringState === 'complete') {\n\t\t\t\tresolve();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet settled = false;\n\t\t\tconst finish = () => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tpc.removeEventListener('icecandidate', onCandidate);\n\t\t\t\tpc.removeEventListener('icegatheringstatechange', onGatheringComplete);\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tconst timeout = setTimeout(finish, ICE_FIRST_RELAY_TIMEOUT_MS);\n\t\t\tconst onCandidate = (e: RTCPeerConnectionIceEvent) => {\n\t\t\t\tif (e.candidate && e.candidate.type === 'relay') finish();\n\t\t\t};\n\t\t\tconst onGatheringComplete = () => {\n\t\t\t\tif (pc.iceGatheringState === 'complete') finish();\n\t\t\t};\n\t\t\tpc.addEventListener('icecandidate', onCandidate);\n\t\t\tpc.addEventListener('icegatheringstatechange', onGatheringComplete);\n\t\t});\n\t}\n\n\tprivate attachRemoteAudio(stream: MediaStream, options: StartCallOptions): void {\n\t\tif (options.audioElement === null) return; // consumer opted out\n\t\tif (!this.remoteAudio) {\n\t\t\tif (options.audioElement) {\n\t\t\t\tthis.remoteAudio = options.audioElement;\n\t\t\t\tthis.ownsAudioElement = false;\n\t\t\t} else {\n\t\t\t\tthis.remoteAudio = new Audio();\n\t\t\t\tthis.remoteAudio.autoplay = true;\n\t\t\t\tthis.ownsAudioElement = true;\n\t\t\t}\n\t\t}\n\t\tthis.remoteAudio.srcObject = stream;\n\t}\n\n\t// -- data-channel protocol ------------------------------------------------\n\n\tprivate handleSignalling(message: SignallingPayload): void {\n\t\tswitch (message.type) {\n\t\t\tcase 'peerLeft':\n\t\t\t\tif (this._state === 'connected' || this._state === 'connecting') {\n\t\t\t\t\tthis.teardown();\n\t\t\t\t\tthis.setState('ended');\n\t\t\t\t\tthis.emitter.emit('ended', { reason: 'peer-left' });\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'renegotiate':\n\t\t\t\tvoid this.renegotiate();\n\t\t\t\tbreak;\n\t\t\tcase 'trackStatus':\n\t\t\t\t// Bot→client trackStatus is not part of the current protocol; ignore.\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Bot asked for a new offer/answer cycle. Re-offer through the same relay\n\t * endpoint. If the backend rejects re-offers (older deployments), the call\n\t * keeps running on the existing session — surfaced via `renegotiated`.\n\t */\n\tprivate async renegotiate(): Promise<void> {\n\t\tif (!this.pc || !this._callId || this.renegotiating) return;\n\t\tthis.renegotiating = true;\n\t\ttry {\n\t\t\tconst offer = await this.pc.createOffer({ iceRestart: true });\n\t\t\tawait this.pc.setLocalDescription(offer);\n\t\t\tawait this.waitForFirstRelayCandidate(this.pc);\n\t\t\tconst answer = await this.signaling.sendOffer(this._callId, {\n\t\t\t\tsdp: this.mustLocalSdp(),\n\t\t\t\ticeServers: this.iceServers,\n\t\t\t});\n\t\t\tawait this.pc.setRemoteDescription({ type: 'answer', sdp: answer.sdp });\n\t\t\tthis.emitter.emit('renegotiated', { ok: true });\n\t\t} catch {\n\t\t\tthis.emitter.emit('renegotiated', { ok: false });\n\t\t} finally {\n\t\t\tthis.renegotiating = false;\n\t\t}\n\t}\n\n\t// -- realtime events (public mode) ----------------------------------------\n\n\t/**\n\t * Public mode only: open the realtime events WebSocket for this call.\n\t * Events double as the pipeline-health signal, and a terminal `status`\n\t * event ends the call from the server side (e.g. max-duration reached).\n\t */\n\tprivate connectEvents(): void {\n\t\tconst session = this.signaling.session;\n\t\tif (!session) return; // authenticated mode: dashboard SSE covers events\n\n\t\tthis.events = new CallEventsClient({\n\t\t\tbaseUrl: this.config.baseUrl,\n\t\t\tgetToken: () => session.getToken(),\n\t\t\tonEvent: (event) => this.handleCallEvent(event),\n\t\t});\n\t\tthis.events.connect();\n\t}\n\n\tprivate handleCallEvent(event: CallEventEnvelope): void {\n\t\t// Events flowing at all = the bot pipeline is alive.\n\t\tthis.confirmPipelineHealthy();\n\t\tthis.emitter.emit('callevent', { event });\n\n\t\tswitch (event.type) {\n\t\t\tcase 'transcript': {\n\t\t\t\tconst role = typeof event.data.role === 'string' ? event.data.role : 'unknown';\n\t\t\t\tconst content = typeof event.data.content === 'string' ? event.data.content : '';\n\t\t\t\tthis.emitter.emit('transcript', { role, content, data: event.data });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'interim':\n\t\t\t\tthis.emitter.emit('interim', { data: event.data });\n\t\t\t\tbreak;\n\t\t\tcase 'tts': {\n\t\t\t\tconst text = typeof event.data.text === 'string' ? event.data.text : '';\n\t\t\t\tif (!text) break;\n\t\t\t\tconst timestamp = typeof event.data.timestamp === 'number' ? event.data.timestamp : 0;\n\t\t\t\tthis.emitter.emit('ttschunk', { text, timestamp, data: event.data });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'tts_word': {\n\t\t\t\tconst word = typeof event.data.word === 'string' ? event.data.word : '';\n\t\t\t\tif (!word) break;\n\t\t\t\t// Backend serializes camelCase (`ptsMs`); accept snake_case too\n\t\t\t\t// for resilience against older/other emitters.\n\t\t\t\tconst ptsRaw = event.data.ptsMs ?? event.data.pts_ms;\n\t\t\t\tconst ptsMs = typeof ptsRaw === 'number' ? ptsRaw : null;\n\t\t\t\tconst timestamp = typeof event.data.timestamp === 'number' ? event.data.timestamp : 0;\n\t\t\t\tthis.emitter.emit('ttsword', { word, ptsMs, timestamp, data: event.data });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'status': {\n\t\t\t\tconst status = typeof event.data.status === 'string' ? event.data.status : '';\n\t\t\t\tif (TERMINAL_CALL_STATUSES.has(status.toUpperCase() as never)) {\n\t\t\t\t\tthis.endedExternally();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// -- health watchdog ------------------------------------------------------\n\n\tprivate armHealthWatchdog(callId: string): void {\n\t\tthis.pipelineConfirmed = false;\n\t\tthis.healthTimer = setTimeout(() => {\n\t\t\tthis.healthTimer = null;\n\t\t\tif (this.pipelineConfirmed) return;\n\t\t\tvoid this.verifyPipelineHealth(callId);\n\t\t}, PIPELINE_HEALTH_TIMEOUT_MS);\n\t}\n\n\tprivate async verifyPipelineHealth(callId: string): Promise<void> {\n\t\ttry {\n\t\t\tconst call = await this.signaling.getCall(callId);\n\t\t\tif (this.pipelineConfirmed || !this.pc) return;\n\t\t\tif (TERMINAL_CALL_STATUSES.has(call.status)) {\n\t\t\t\tthis.fail(new TalkifCallError('pipeline-dead', `Backend reports terminal status '${call.status}'`));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Backend says the call is alive — the silence was the event layer's\n\t\t\t// fault, not the pipeline's.\n\t\t\tthis.pipelineConfirmed = true;\n\t\t} catch {\n\t\t\tif (this.pipelineConfirmed || !this.pc) return;\n\t\t\t// REST unreachable — fall back to the media-plane signal. Flowing\n\t\t\t// audio means alive; later media death is caught by\n\t\t\t// onconnectionstatechange. Kill only when both signals are gone.\n\t\t\tif (this.pc.connectionState === 'connected') {\n\t\t\t\tthis.pipelineConfirmed = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.fail(new TalkifCallError('pipeline-dead', 'No health signal, REST unreachable, media not connected'));\n\t\t}\n\t}\n\n\t// -- internals ------------------------------------------------------------\n\n\tprivate mustLocalSdp(): string {\n\t\tconst sdp = this.pc?.localDescription?.sdp;\n\t\tif (!sdp) {\n\t\t\tthrow new TalkifCallError('signaling-failed', 'Local SDP missing');\n\t\t}\n\t\treturn sdp;\n\t}\n\n\tprivate startDurationTimer(): void {\n\t\tthis._durationSecs = 0;\n\t\tthis.durationTimer = setInterval(() => {\n\t\t\tthis._durationSecs += 1;\n\t\t\tthis.emitter.emit('tick', { durationSecs: this._durationSecs });\n\t\t}, 1_000);\n\t}\n\n\tprivate fail(error: TalkifCallError): void {\n\t\tif (this._state === 'ended' || this._state === 'error') return;\n\t\tthis.teardown();\n\t\tthis.setState('error');\n\t\tthis.emitter.emit('error', { error });\n\t}\n\n\tprivate setState(state: CallState): void {\n\t\tconst previous = this._state;\n\t\tif (previous === state) return;\n\t\tthis._state = state;\n\t\tthis.emitter.emit('statechange', { state, previous });\n\t}\n\n\tprivate teardown(): void {\n\t\tthis.events?.close();\n\t\tthis.events = null;\n\n\t\tif (this.healthTimer) {\n\t\t\tclearTimeout(this.healthTimer);\n\t\t\tthis.healthTimer = null;\n\t\t}\n\t\tthis.pipelineConfirmed = false;\n\n\t\tif (this.durationTimer) {\n\t\t\tclearInterval(this.durationTimer);\n\t\t\tthis.durationTimer = null;\n\t\t}\n\n\t\tthis.dataChannel?.close();\n\t\tthis.dataChannel = null;\n\n\t\tif (this.localStream) {\n\t\t\tfor (const track of this.localStream.getTracks()) track.stop();\n\t\t\tthis.localStream = null;\n\t\t}\n\n\t\tif (this.remoteAudio) {\n\t\t\tthis.remoteAudio.pause();\n\t\t\tthis.remoteAudio.srcObject = null;\n\t\t\tif (this.ownsAudioElement) this.remoteAudio = null;\n\t\t}\n\n\t\tif (this.pc) {\n\t\t\tthis.pc.ontrack = null;\n\t\t\tthis.pc.onconnectionstatechange = null;\n\t\t\tthis.pc.close();\n\t\t\tthis.pc = null;\n\t\t}\n\t}\n}\n\nexport type { CallEndReason };\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -381,7 +381,6 @@ declare class CallEventsClient {
381
381
  private ws;
382
382
  private closed;
383
383
  private attempts;
384
- private everConnected;
385
384
  private lastSeq;
386
385
  private reconnectTimer;
387
386
  constructor(options: CallEventsOptions);
package/dist/index.d.ts CHANGED
@@ -381,7 +381,6 @@ declare class CallEventsClient {
381
381
  private ws;
382
382
  private closed;
383
383
  private attempts;
384
- private everConnected;
385
384
  private lastSeq;
386
385
  private reconnectTimer;
387
386
  constructor(options: CallEventsOptions);
package/dist/index.js CHANGED
@@ -98,7 +98,6 @@ var CallEventsClient = class {
98
98
  ws = null;
99
99
  closed = false;
100
100
  attempts = 0;
101
- everConnected = false;
102
101
  lastSeq = 0;
103
102
  reconnectTimer = null;
104
103
  constructor(options) {
@@ -135,9 +134,7 @@ var CallEventsClient = class {
135
134
  this.ws = ws;
136
135
  ws.onopen = () => {
137
136
  ws.send(JSON.stringify({ type: "auth", token }));
138
- ws.send(
139
- JSON.stringify({ type: "subscribe", ...this.everConnected ? { replay: true } : {} })
140
- );
137
+ ws.send(JSON.stringify({ type: "subscribe", replay: true }));
141
138
  };
142
139
  ws.onmessage = (message) => {
143
140
  if (typeof message.data !== "string") return;
@@ -149,7 +146,6 @@ var CallEventsClient = class {
149
146
  }
150
147
  if (typeof envelope.type !== "string" || typeof envelope.seq !== "number") return;
151
148
  if (envelope.type === "subscribed") {
152
- this.everConnected = true;
153
149
  this.attempts = 0;
154
150
  this.lastSeq = envelope.seq;
155
151
  return;
@@ -613,6 +609,7 @@ var TalkifCall = class {
613
609
  this.pc = local.pc;
614
610
  this.localStream = local.stream;
615
611
  this.setState("connecting");
612
+ this.connectEvents();
616
613
  this.pc.ontrack = (event) => {
617
614
  const stream = event.streams[0];
618
615
  if (!stream) return;
@@ -639,7 +636,6 @@ var TalkifCall = class {
639
636
  this.emitter.emit("connected", { callId: callResponse.callId, botId: this._botId });
640
637
  this.startDurationTimer();
641
638
  this.armHealthWatchdog(callResponse.callId);
642
- this.connectEvents();
643
639
  } catch (error) {
644
640
  this.fail(
645
641
  error instanceof TalkifCallError ? error : asSignalingCallError(error)
@@ -795,7 +791,8 @@ var TalkifCall = class {
795
791
  case "tts_word": {
796
792
  const word = typeof event.data.word === "string" ? event.data.word : "";
797
793
  if (!word) break;
798
- const ptsMs = typeof event.data.pts_ms === "number" ? event.data.pts_ms : null;
794
+ const ptsRaw = event.data.ptsMs ?? event.data.pts_ms;
795
+ const ptsMs = typeof ptsRaw === "number" ? ptsRaw : null;
799
796
  const timestamp = typeof event.data.timestamp === "number" ? event.data.timestamp : 0;
800
797
  this.emitter.emit("ttsword", { word, ptsMs, timestamp, data: event.data });
801
798
  break;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/data-channel.ts","../src/emitter.ts","../src/events.ts","../src/types.ts","../src/turnstile.ts","../src/signaling.ts","../src/call.ts"],"sourcesContent":["import type { AppMessage, SignallingPayload, TrackStatusMessage } from './types.js';\n\nconst KEEPALIVE_INTERVAL_MS = 2_000;\n\nexport interface DataChannelHandlers {\n\tonSignalling: (message: SignallingPayload) => void;\n\tonAppMessage: (message: AppMessage) => void;\n}\n\n/**\n * Client side of the pipecat SmallWebRTC data-channel protocol:\n *\n * - `\"ping\"` strings every 2s keep the bot's liveness check happy (the bot\n * treats a connection as active when the last ping is <3s old, with the\n * ICE connection state as fallback).\n * - Inbound JSON is either `{type: \"signalling\", message: {...}}` (protocol)\n * or an arbitrary app message (forwarded to the consumer).\n */\nexport class DataChannel {\n\tprivate channel: RTCDataChannel;\n\tprivate keepalive: ReturnType<typeof setInterval> | null = null;\n\tprivate readonly handlers: DataChannelHandlers;\n\n\tconstructor(pc: RTCPeerConnection, handlers: DataChannelHandlers) {\n\t\tthis.handlers = handlers;\n\t\tthis.channel = pc.createDataChannel('pipecat', { ordered: true });\n\t\tthis.channel.onopen = () => this.startKeepalive();\n\t\tthis.channel.onclose = () => this.stopKeepalive();\n\t\tthis.channel.onmessage = (event) => this.handleMessage(event.data);\n\t}\n\n\tprivate startKeepalive(): void {\n\t\tthis.stopKeepalive();\n\t\tthis.keepalive = setInterval(() => {\n\t\t\tif (this.channel.readyState === 'open') {\n\t\t\t\tthis.channel.send('ping');\n\t\t\t}\n\t\t}, KEEPALIVE_INTERVAL_MS);\n\t}\n\n\tprivate stopKeepalive(): void {\n\t\tif (this.keepalive) {\n\t\t\tclearInterval(this.keepalive);\n\t\t\tthis.keepalive = null;\n\t\t}\n\t}\n\n\tprivate handleMessage(data: unknown): void {\n\t\tif (typeof data !== 'string') return;\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(data);\n\t\t} catch {\n\t\t\treturn; // Non-JSON payloads (e.g. bot-side pings) carry no protocol meaning here.\n\t\t}\n\t\tif (typeof parsed !== 'object' || parsed === null) return;\n\n\t\tconst record = parsed as Record<string, unknown>;\n\t\tif (record['type'] === 'signalling' && typeof record['message'] === 'object' && record['message'] !== null) {\n\t\t\tthis.handlers.onSignalling(record['message'] as SignallingPayload);\n\t\t\treturn;\n\t\t}\n\t\tthis.handlers.onAppMessage(record as AppMessage);\n\t}\n\n\t/** Send an arbitrary JSON app message to the bot pipeline. */\n\tsendAppMessage(message: AppMessage): boolean {\n\t\tif (this.channel.readyState !== 'open') return false;\n\t\tthis.channel.send(JSON.stringify(message));\n\t\treturn true;\n\t}\n\n\t/** Tell the bot to enable/disable one of our media receivers. */\n\tsendTrackStatus(message: Omit<TrackStatusMessage, 'type'>): boolean {\n\t\tif (this.channel.readyState !== 'open') return false;\n\t\tthis.channel.send(JSON.stringify({ type: 'signalling', message: { type: 'trackStatus', ...message } }));\n\t\treturn true;\n\t}\n\n\tclose(): void {\n\t\tthis.stopKeepalive();\n\t\tthis.channel.onopen = null;\n\t\tthis.channel.onclose = null;\n\t\tthis.channel.onmessage = null;\n\t\tif (this.channel.readyState === 'open' || this.channel.readyState === 'connecting') {\n\t\t\tthis.channel.close();\n\t\t}\n\t}\n}\n","/** Minimal typed event emitter — no dependency, browser-safe. */\nexport class Emitter<Events extends object> {\n\tprivate listeners = new Map<keyof Events, Set<(payload: never) => void>>();\n\n\ton<K extends keyof Events>(event: K, listener: (payload: Events[K]) => void): () => void {\n\t\tlet set = this.listeners.get(event);\n\t\tif (!set) {\n\t\t\tset = new Set();\n\t\t\tthis.listeners.set(event, set);\n\t\t}\n\t\tset.add(listener as (payload: never) => void);\n\t\treturn () => this.off(event, listener);\n\t}\n\n\toff<K extends keyof Events>(event: K, listener: (payload: Events[K]) => void): void {\n\t\tthis.listeners.get(event)?.delete(listener as (payload: never) => void);\n\t}\n\n\temit<K extends keyof Events>(event: K, payload: Events[K]): void {\n\t\tconst set = this.listeners.get(event);\n\t\tif (!set) return;\n\t\tfor (const listener of [...set]) {\n\t\t\t(listener as (payload: Events[K]) => void)(payload);\n\t\t}\n\t}\n\n\tremoveAll(): void {\n\t\tthis.listeners.clear();\n\t}\n}\n","import type { CallEventEnvelope } from './types.js';\n\n/** Reconnect backoff schedule (ms). Caps at the last entry. */\nconst BACKOFF_MS = [500, 1_000, 2_000, 4_000, 8_000];\n\nexport interface CallEventsOptions {\n\t/** API origin, e.g. `https://api.talkif.ai`. */\n\tbaseUrl: string;\n\t/** Fresh session token per (re)connect — first-message auth. */\n\tgetToken: () => Promise<string>;\n\t/** Every delivered envelope for the session's call. */\n\tonEvent: (event: CallEventEnvelope) => void;\n\t/**\n\t * A `seq` gap was observed: this connection shed frames under backpressure.\n\t * The client auto-requests a server-side replay on reconnect; consumers that\n\t * render transcripts incrementally may want to clear-and-rebuild on replay.\n\t */\n\tonGap?: (expected: number, received: number) => void;\n\t/** Optional WebSocket constructor override (tests, Node). */\n\twebSocket?: typeof WebSocket;\n}\n\n/**\n * Client for the public realtime events WebSocket\n * (`/api/v1/public/ws/events`).\n *\n * Protocol: connect → send `{\"type\":\"auth\",\"token\":...}` within 5s → send\n * `{\"type\":\"subscribe\"}` (the session is auto-bound to its one call) →\n * receive `{v, type, call_id?, seq, ts, data}` envelopes. Reconnects with\n * backoff; after the first successful connection, reconnect subscribes with\n * `replay: true` to recover anything missed during the gap.\n */\nexport class CallEventsClient {\n\tprivate readonly options: CallEventsOptions;\n\tprivate ws: WebSocket | null = null;\n\tprivate closed = false;\n\tprivate attempts = 0;\n\tprivate everConnected = false;\n\tprivate lastSeq = 0;\n\tprivate reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n\tconstructor(options: CallEventsOptions) {\n\t\tthis.options = options;\n\t}\n\n\tconnect(): void {\n\t\tif (this.closed || this.ws) return;\n\t\tvoid this.open();\n\t}\n\n\tclose(): void {\n\t\tthis.closed = true;\n\t\tif (this.reconnectTimer) {\n\t\t\tclearTimeout(this.reconnectTimer);\n\t\t\tthis.reconnectTimer = null;\n\t\t}\n\t\tthis.ws?.close(1000);\n\t\tthis.ws = null;\n\t}\n\n\tprivate wsUrl(): string {\n\t\tconst base = this.options.baseUrl.replace(/\\/$/, '').replace(/^http/, 'ws');\n\t\treturn `${base}/api/v1/public/ws/events`;\n\t}\n\n\tprivate async open(): Promise<void> {\n\t\tlet token: string;\n\t\ttry {\n\t\t\ttoken = await this.options.getToken();\n\t\t} catch {\n\t\t\tthis.scheduleReconnect();\n\t\t\treturn;\n\t\t}\n\t\tif (this.closed) return;\n\n\t\tconst WS = this.options.webSocket ?? WebSocket;\n\t\tconst ws = new WS(this.wsUrl());\n\t\tthis.ws = ws;\n\n\t\tws.onopen = () => {\n\t\t\tws.send(JSON.stringify({ type: 'auth', token }));\n\t\t\t// Auto-bound to the session's call; replay recovers a reconnect gap.\n\t\t\tws.send(\n\t\t\t\tJSON.stringify({ type: 'subscribe', ...(this.everConnected ? { replay: true } : {}) })\n\t\t\t);\n\t\t};\n\n\t\tws.onmessage = (message: MessageEvent) => {\n\t\t\tif (typeof message.data !== 'string') return;\n\t\t\tlet envelope: CallEventEnvelope;\n\t\t\ttry {\n\t\t\t\tenvelope = JSON.parse(message.data) as CallEventEnvelope;\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (typeof envelope.type !== 'string' || typeof envelope.seq !== 'number') return;\n\n\t\t\tif (envelope.type === 'subscribed') {\n\t\t\t\tthis.everConnected = true;\n\t\t\t\tthis.attempts = 0;\n\t\t\t\tthis.lastSeq = envelope.seq;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (envelope.type === 'pong' || envelope.type === 'unsubscribed') {\n\t\t\t\tthis.lastSeq = envelope.seq;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this.lastSeq > 0 && envelope.seq > this.lastSeq + 1) {\n\t\t\t\tthis.options.onGap?.(this.lastSeq + 1, envelope.seq);\n\t\t\t}\n\t\t\tthis.lastSeq = envelope.seq;\n\t\t\tthis.options.onEvent(envelope);\n\t\t};\n\n\t\tws.onclose = (event: CloseEvent) => {\n\t\t\tif (this.ws !== ws) return;\n\t\t\tthis.ws = null;\n\t\t\t// seq is per-connection — reset the tracker.\n\t\t\tthis.lastSeq = 0;\n\t\t\t// 4401/4403 are auth/scope rejections: retrying with the same session\n\t\t\t// can still succeed after a token refresh (getToken re-exchanges), but\n\t\t\t// a scope violation (4403) will never heal — stop there.\n\t\t\tif (event.code === 4403) {\n\t\t\t\tthis.closed = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.scheduleReconnect();\n\t\t};\n\n\t\tws.onerror = () => {\n\t\t\t// onclose follows and handles reconnection.\n\t\t};\n\t}\n\n\tprivate scheduleReconnect(): void {\n\t\tif (this.closed || this.reconnectTimer) return;\n\t\tconst delay = BACKOFF_MS[Math.min(this.attempts, BACKOFF_MS.length - 1)] ?? 8_000;\n\t\tthis.attempts += 1;\n\t\tthis.reconnectTimer = setTimeout(() => {\n\t\t\tthis.reconnectTimer = null;\n\t\t\tvoid this.open();\n\t\t}, delay);\n\t}\n}\n","/**\n * Wire types for the Talkif WebRTC signaling API and the pipecat SmallWebRTC\n * data-channel protocol spoken by Talkif voice bots.\n *\n * These shapes are the public contract. Field casing matches the backend JSON\n * exactly (camelCase over HTTP).\n */\n\n// ---------------------------------------------------------------------------\n// HTTP signaling\n// ---------------------------------------------------------------------------\n\nexport interface IceServer {\n\turls: string;\n\tusername?: string;\n\tcredential?: string;\n}\n\nexport interface IceServersResponse {\n\ticeServers: IceServer[];\n\t/** Seconds remaining on the underlying TURN credential. */\n\tttl: number;\n}\n\nexport interface CreateCallRequest {\n\tflowId: string;\n\tmetadata?: Record<string, unknown>;\n}\n\nexport interface CreateTestCallRequest {\n\tflowId: string;\n\t/**\n\t * Draft flow definition (frontend `FlowDefinition` shape). The library does\n\t * not interpret it — it is passed through to the backend, which validates\n\t * and compiles it for the bot.\n\t */\n\tdefinition: Record<string, unknown>;\n\tmetadata?: Record<string, unknown>;\n}\n\nexport type CallStatus =\n\t| 'CREATED'\n\t| 'BOT_ASSIGNED'\n\t| 'QUEUED'\n\t| 'RINGING'\n\t| 'IN_PROGRESS'\n\t| 'COMPLETED'\n\t| 'FAILED'\n\t| 'NO_ANSWER'\n\t| 'BUSY'\n\t| 'CANCELED';\n\n/** REST statuses that mean the call is genuinely over. */\nexport const TERMINAL_CALL_STATUSES: ReadonlySet<CallStatus> = new Set([\n\t'COMPLETED',\n\t'FAILED',\n\t'NO_ANSWER',\n\t'BUSY',\n\t'CANCELED',\n]);\n\nexport interface CreateCallResponse {\n\tcallId: string;\n\tflowId: string;\n\tstatus: CallStatus;\n\tbotId?: string | null;\n}\n\nexport interface WebRTCCall {\n\tcallId: string;\n\tstatus: CallStatus;\n}\n\nexport interface OfferRequest {\n\tsdp: string;\n\ticeServers?: IceServer[];\n}\n\n/** Public surface: token-exchange response (`POST /public/calls/session`). */\nexport interface PublicSessionResponse {\n\t/** Signed session token — sent as `Authorization: Bearer <token>`. */\n\ttoken: string;\n\t/** Seconds until the token expires (~15 min). */\n\texpiresIn: number;\n\t/** The flow this session may call (bound to the publishable key). */\n\tflowId: string;\n}\n\nexport interface OfferResponse {\n\tsdp: string;\n\tsdpType: string;\n\tbotId?: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Data-channel protocol (pipecat SmallWebRTC — bot side)\n// ---------------------------------------------------------------------------\n\n/** Bot → client: bot requests a new offer/answer cycle. */\nexport interface RenegotiateMessage {\n\ttype: 'renegotiate';\n}\n\n/** Bot → client: bot is closing the connection. */\nexport interface PeerLeftMessage {\n\ttype: 'peerLeft';\n}\n\n/** Client → bot: enable/disable a media receiver. 0=audio, 1=video, 2=screen. */\nexport interface TrackStatusMessage {\n\ttype: 'trackStatus';\n\treceiver_index: 0 | 1 | 2;\n\tenabled: boolean;\n}\n\nexport type SignallingPayload = RenegotiateMessage | PeerLeftMessage | TrackStatusMessage;\n\nexport interface SignallingEnvelope {\n\ttype: 'signalling';\n\tmessage: SignallingPayload;\n}\n\n/**\n * Anything on the data channel that is not the signalling envelope (and not a\n * `\"ping\"` keepalive string) is an app message: arbitrary JSON exchanged with\n * the bot pipeline.\n */\nexport type AppMessage = Record<string, unknown>;\n\n// ---------------------------------------------------------------------------\n// Realtime events WebSocket (`/api/v1/public/ws/events`)\n// ---------------------------------------------------------------------------\n\n/**\n * Server envelope on the realtime events WebSocket. `seq` is per-connection\n * and monotonic — a gap means THIS connection dropped frames under\n * backpressure, not that the call missed anything.\n */\nexport interface CallEventEnvelope {\n\tv: number;\n\ttype: string;\n\tcall_id?: string;\n\tseq: number;\n\tts: number;\n\tdata: Record<string, unknown>;\n}\n\n/** Event types the public WS delivers for the session's call. */\nexport type CallEventType =\n\t| 'transcript'\n\t| 'interim'\n\t| 'status'\n\t| 'tts'\n\t| 'tts_word'\n\t| 'speech'\n\t| 'turn'\n\t| 'node_transition'\n\t| 'error';\n\n// ---------------------------------------------------------------------------\n// Client state machine & events\n// ---------------------------------------------------------------------------\n\nexport type CallState =\n\t| 'idle'\n\t| 'requesting'\n\t| 'connecting'\n\t| 'connected'\n\t| 'ended'\n\t| 'error';\n\nexport interface TalkifCallEventMap {\n\t/** State machine transition. */\n\tstatechange: { state: CallState; previous: CallState };\n\t/** Media is flowing and the SDP answer is applied. */\n\tconnected: { callId: string; botId: string | null };\n\t/** Remote (bot) audio track arrived. */\n\ttrack: { stream: MediaStream };\n\t/** Call finished normally (user hangup, bot peerLeft, terminal status). */\n\tended: { reason: CallEndReason };\n\t/** Fatal failure. The call is torn down; state is 'error'. */\n\terror: { error: TalkifCallError };\n\t/** One-second duration tick while connected. */\n\ttick: { durationSecs: number };\n\t/** JSON app message from the bot over the data channel. */\n\tappmessage: { message: AppMessage };\n\t/** Bot asked for renegotiation. Emitted after the re-offer is attempted. */\n\trenegotiated: { ok: boolean };\n\t/** Mute toggled. */\n\tmutechange: { muted: boolean };\n\t/**\n\t * Any realtime event for this call from the events WebSocket (public mode).\n\t * Fires for every delivered envelope, including those with dedicated\n\t * events below.\n\t */\n\tcallevent: { event: CallEventEnvelope };\n\t/** Final transcript line (public mode, via the events WebSocket). */\n\ttranscript: { role: string; content: string; data: Record<string, unknown> };\n\t/** Interim (in-progress) transcription (public mode). May be dropped under backpressure. */\n\tinterim: { data: Record<string, unknown> };\n\t/**\n\t * Word-level TTS timing (public mode). Drives karaoke-style caption\n\t * highlighting; only words actually spoken are delivered. May be dropped\n\t * under backpressure.\n\t */\n\tttsword: TtsWordEvent;\n\t/**\n\t * Sentence-level chunk of the agent's reply (public mode), emitted when the\n\t * LLM output is handed to TTS synthesis — ahead of audio playback. Use for\n\t * chat-style streaming of the reply; reconcile with the final `transcript`\n\t * (on interruption the tail may never be spoken). May be dropped under\n\t * backpressure.\n\t */\n\tttschunk: TtsChunkEvent;\n}\n\n/** Payload of a `tts` realtime event. */\nexport interface TtsChunkEvent {\n\t/** Sentence-level text chunk of the agent's reply. */\n\ttext: string;\n\t/** Unix timestamp from the bot (seconds, fractional). */\n\ttimestamp: number;\n\t/** Full raw event payload (character counts, TTS timing metrics…). */\n\tdata: Record<string, unknown>;\n}\n\n/** Payload of a `tts_word` realtime event. */\nexport interface TtsWordEvent {\n\t/** The spoken word. */\n\tword: string;\n\t/** Presentation timestamp — when the word is spoken in the audio (ms), if known. */\n\tptsMs: number | null;\n\t/** Unix timestamp from the bot (seconds, fractional). */\n\ttimestamp: number;\n\t/** Full raw event payload. */\n\tdata: Record<string, unknown>;\n}\n\nexport type CallEndReason =\n\t| 'local-hangup'\n\t| 'peer-left'\n\t| 'terminal-status'\n\t| 'external';\n\nexport type TalkifCallErrorCode =\n\t| 'media-unavailable'\n\t| 'signaling-failed'\n\t| 'connection-failed'\n\t| 'pipeline-dead'\n\t| 'renegotiation-failed'\n\t/** Publishable key rejected, origin not allowed, or session expired. */\n\t| 'session-rejected'\n\t/** This session already has an active call (multi-tab). */\n\t| 'call-active'\n\t/** Rate/concurrency limit hit — retry later. */\n\t| 'rate-limited'\n\t/** No bot available right now — retry shortly. */\n\t| 'no-agent';\n\nexport class TalkifCallError extends Error {\n\treadonly code: TalkifCallErrorCode;\n\toverride readonly cause?: unknown;\n\n\tconstructor(code: TalkifCallErrorCode, message: string, cause?: unknown) {\n\t\tsuper(message);\n\t\tthis.name = 'TalkifCallError';\n\t\tthis.code = code;\n\t\tthis.cause = cause;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Configuration\n// ---------------------------------------------------------------------------\n\n/**\n * Supplies the Authorization header value for signaling requests.\n * Called per request so short-lived tokens (JWT refresh, widget session\n * tokens) stay current. Return e.g. `\"Bearer <token>\"`.\n */\nexport type AuthProvider = () => string | Promise<string>;\n\nexport interface TalkifClientConfig {\n\t/** API origin, e.g. `https://api.talkif.ai`. No trailing slash. */\n\tbaseUrl: string;\n\taccountId: string;\n\tauth: AuthProvider;\n\t/** Override fetch (tests, SSR polyfills). Defaults to global fetch. */\n\tfetch?: typeof fetch;\n}\n\n/**\n * Public (embeddable) configuration: the browser-safe publishable key from the\n * flow's public-access settings. The library exchanges it for a short-lived\n * session token itself — no bearer token, no account id. The page's Origin\n * must be on the key's allowlist.\n */\nexport interface TalkifPublicClientConfig {\n\t/** API origin, e.g. `https://api.talkif.ai`. No trailing slash. */\n\tbaseUrl: string;\n\t/** Publishable key (`pk_live_...`) — safe to ship in page source. */\n\tpublishableKey: string;\n\t/**\n\t * Cloudflare Turnstile sitekey (Talkif's — public, safe to ship). When set,\n\t * the library renders an invisible Turnstile challenge and produces a token\n\t * for each session exchange automatically. Leave unset to disable the gate\n\t * (dev/local, or before Turnstile is provisioned).\n\t */\n\tturnstileSiteKey?: string;\n\t/**\n\t * Manual Turnstile token supplier. Overrides `turnstileSiteKey` — supply\n\t * this only if you render Turnstile yourself. Called at each exchange.\n\t */\n\tturnstileToken?: () => string | Promise<string>;\n\t/** Override fetch (tests, SSR polyfills). Defaults to global fetch. */\n\tfetch?: typeof fetch;\n}\n\nexport type TalkifConfig = TalkifClientConfig | TalkifPublicClientConfig;\n\nexport function isPublicConfig(config: TalkifConfig): config is TalkifPublicClientConfig {\n\treturn 'publishableKey' in config;\n}\n\nexport interface StartCallOptions {\n\t/**\n\t * Start a call against a published flow. Required in authenticated mode.\n\t * Ignored in public mode — the flow is bound to the publishable key.\n\t */\n\tflowId?: string;\n\t/**\n\t * When set, uses the draft test-call endpoint with this definition instead\n\t * of the published flow.\n\t */\n\tdraftDefinition?: Record<string, unknown>;\n\tmetadata?: Record<string, unknown>;\n\t/** Constraints for getUserMedia. Defaults to `{ audio: true }`. */\n\taudio?: MediaTrackConstraints | boolean;\n\t/**\n\t * Attach remote audio to this element instead of an internally created\n\t * autoplay `Audio()`. Pass `null` to disable playback entirely (consumer\n\t * handles the `track` event itself).\n\t */\n\taudioElement?: HTMLAudioElement | null;\n}\n","/**\n * Cloudflare Turnstile integration — the \"prove you're a real browser\" gate\n * that stops scripted abuse of the public call surface.\n *\n * The customer does nothing: the widget self-injects Cloudflare's script,\n * renders an invisible Turnstile widget with Talkif's sitekey, and produces a\n * fresh token for each session exchange. The sitekey is public and safe to\n * ship; the matching secret lives only in the backend.\n */\n\nconst TURNSTILE_SCRIPT_URL =\n\t'https://challenges.cloudflare.com/turnstile/v0/api.js';\n\n/** Minimal shape of the global `turnstile` object we use. */\ninterface TurnstileApi {\n\trender(\n\t\tcontainer: HTMLElement,\n\t\toptions: {\n\t\t\tsitekey: string;\n\t\t\tcallback?: (token: string) => void;\n\t\t\t'error-callback'?: (code?: string) => void;\n\t\t\t'expired-callback'?: () => void;\n\t\t\tsize?: 'invisible' | 'normal' | 'compact' | 'flexible';\n\t\t\texecution?: 'render' | 'execute';\n\t\t\tretry?: 'auto' | 'never';\n\t\t}\n\t): string;\n\texecute(widgetId: string, options?: { sitekey?: string }): void;\n\treset(widgetId: string): void;\n\tremove(widgetId: string): void;\n}\n\ndeclare global {\n\tinterface Window {\n\t\tturnstile?: TurnstileApi;\n\t}\n}\n\nlet scriptPromise: Promise<void> | null = null;\n\n/** Load Cloudflare's Turnstile script once, shared across all instances. */\nfunction loadScript(): Promise<void> {\n\tif (typeof document === 'undefined') {\n\t\treturn Promise.reject(new Error('Turnstile requires a browser environment'));\n\t}\n\tif (window.turnstile) return Promise.resolve();\n\tscriptPromise ??= new Promise<void>((resolve, reject) => {\n\t\tconst existing = document.querySelector<HTMLScriptElement>(\n\t\t\t`script[src=\"${TURNSTILE_SCRIPT_URL}\"]`\n\t\t);\n\t\tif (existing) {\n\t\t\texisting.addEventListener('load', () => resolve());\n\t\t\texisting.addEventListener('error', () => reject(new Error('Turnstile script failed to load')));\n\t\t\tif (window.turnstile) resolve();\n\t\t\treturn;\n\t\t}\n\t\tconst script = document.createElement('script');\n\t\tscript.src = TURNSTILE_SCRIPT_URL;\n\t\tscript.async = true;\n\t\tscript.defer = true;\n\t\tscript.onload = () => resolve();\n\t\tscript.onerror = () => reject(new Error('Turnstile script failed to load'));\n\t\tdocument.head.appendChild(script);\n\t});\n\treturn scriptPromise;\n}\n\n/**\n * Manages one invisible Turnstile widget and hands out fresh tokens.\n *\n * Turnstile tokens are single-use and short-lived, so we `execute()` a fresh\n * challenge for each call to `getToken()` (i.e. each session exchange). The\n * widget is rendered lazily on first use and torn down with `dispose()`.\n */\nexport class TurnstileManager {\n\tprivate readonly siteKey: string;\n\tprivate container: HTMLDivElement | null = null;\n\tprivate widgetId: string | null = null;\n\tprivate pending: {\n\t\tresolve: (token: string) => void;\n\t\treject: (err: Error) => void;\n\t} | null = null;\n\n\tconstructor(siteKey: string) {\n\t\tthis.siteKey = siteKey;\n\t}\n\n\t/** Obtain a fresh Turnstile token. Rejects if the challenge fails. */\n\tasync getToken(): Promise<string> {\n\t\tawait loadScript();\n\t\tconst turnstile = window.turnstile;\n\t\tif (!turnstile) throw new Error('Turnstile unavailable after script load');\n\n\t\tif (!this.container) {\n\t\t\t// A hidden, off-screen host — the invisible widget never shows UI\n\t\t\t// but Cloudflare still requires an element to render into.\n\t\t\tthis.container = document.createElement('div');\n\t\t\tthis.container.style.position = 'fixed';\n\t\t\tthis.container.style.width = '0';\n\t\t\tthis.container.style.height = '0';\n\t\t\tthis.container.style.overflow = 'hidden';\n\t\t\tthis.container.style.pointerEvents = 'none';\n\t\t\tthis.container.setAttribute('aria-hidden', 'true');\n\t\t\tdocument.body.appendChild(this.container);\n\t\t}\n\n\t\tif (this.widgetId === null) {\n\t\t\tthis.widgetId = turnstile.render(this.container, {\n\t\t\t\tsitekey: this.siteKey,\n\t\t\t\tsize: 'invisible',\n\t\t\t\texecution: 'execute',\n\t\t\t\tretry: 'auto',\n\t\t\t\tcallback: (token: string) => {\n\t\t\t\t\tthis.pending?.resolve(token);\n\t\t\t\t\tthis.pending = null;\n\t\t\t\t},\n\t\t\t\t'error-callback': (code?: string) => {\n\t\t\t\t\tthis.pending?.reject(new Error(`Turnstile challenge failed${code ? `: ${code}` : ''}`));\n\t\t\t\t\tthis.pending = null;\n\t\t\t\t},\n\t\t\t\t'expired-callback': () => {\n\t\t\t\t\t// Token expired before use — the next getToken() re-executes.\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\treturn new Promise<string>((resolve, reject) => {\n\t\t\t// Only one outstanding challenge at a time; supersede any prior.\n\t\t\tif (this.pending) {\n\t\t\t\tthis.pending.reject(new Error('Turnstile challenge superseded'));\n\t\t\t}\n\t\t\tthis.pending = { resolve, reject };\n\t\t\tconst id = this.widgetId;\n\t\t\tif (id === null) {\n\t\t\t\treject(new Error('Turnstile widget not rendered'));\n\t\t\t\tthis.pending = null;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tturnstile.reset(id);\n\t\t\t\tturnstile.execute(id, { sitekey: this.siteKey });\n\t\t\t} catch (err) {\n\t\t\t\tthis.pending = null;\n\t\t\t\treject(err instanceof Error ? err : new Error('Turnstile execute failed'));\n\t\t\t}\n\t\t});\n\t}\n\n\tdispose(): void {\n\t\tif (this.widgetId !== null && window.turnstile) {\n\t\t\ttry {\n\t\t\t\twindow.turnstile.remove(this.widgetId);\n\t\t\t} catch {\n\t\t\t\t// best-effort\n\t\t\t}\n\t\t}\n\t\tthis.widgetId = null;\n\t\tthis.pending = null;\n\t\tif (this.container) {\n\t\t\tthis.container.remove();\n\t\t\tthis.container = null;\n\t\t}\n\t}\n}\n","import type {\n\tCreateCallRequest,\n\tCreateCallResponse,\n\tCreateTestCallRequest,\n\tIceServersResponse,\n\tOfferRequest,\n\tOfferResponse,\n\tPublicSessionResponse,\n\tTalkifConfig,\n\tTalkifPublicClientConfig,\n\tWebRTCCall,\n} from './types.js';\nimport { isPublicConfig, TalkifCallError } from './types.js';\nimport { TurnstileManager } from './turnstile.js';\n\nexport class SignalingError extends Error {\n\treadonly status: number;\n\treadonly body: unknown;\n\n\tconstructor(status: number, message: string, body: unknown) {\n\t\tsuper(message);\n\t\tthis.name = 'SignalingError';\n\t\tthis.status = status;\n\t\tthis.body = body;\n\t}\n}\n\n/** Re-exchange the publishable key this many ms before the token expires. */\nconst SESSION_REFRESH_MARGIN_MS = 60_000;\n\n/**\n * Holds the short-lived session token for the public surface: exchanges the\n * publishable key on first use, re-exchanges near expiry, and can be\n * invalidated on a 401 so the next request retries with a fresh token.\n */\nexport class PublicSession {\n\tprivate readonly config: TalkifPublicClientConfig;\n\tprivate token: string | null = null;\n\tprivate expiresAt = 0;\n\tprivate flowIdValue: string | null = null;\n\tprivate inflight: Promise<string> | null = null;\n\tprivate turnstile: TurnstileManager | null = null;\n\n\tconstructor(config: TalkifPublicClientConfig) {\n\t\tthis.config = config;\n\t\t// Built-in Turnstile: render an invisible challenge from the sitekey,\n\t\t// unless the caller supplies their own token provider.\n\t\tif (config.turnstileSiteKey && !config.turnstileToken) {\n\t\t\tthis.turnstile = new TurnstileManager(config.turnstileSiteKey);\n\t\t}\n\t}\n\n\t/** A fresh Turnstile token, from the manual supplier or the built-in\n\t * manager; undefined when no gate is configured (dev/local). */\n\tprivate async turnstileToken(): Promise<string | undefined> {\n\t\tif (this.config.turnstileToken) {\n\t\t\treturn this.config.turnstileToken();\n\t\t}\n\t\tif (this.turnstile) {\n\t\t\treturn this.turnstile.getToken();\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/** Tear down the Turnstile widget (call when the client is disposed). */\n\tdispose(): void {\n\t\tthis.turnstile?.dispose();\n\t\tthis.turnstile = null;\n\t}\n\n\t/** Flow bound to the publishable key (known after the first exchange). */\n\tget flowId(): string | null {\n\t\treturn this.flowIdValue;\n\t}\n\n\tasync getToken(): Promise<string> {\n\t\tif (this.token && Date.now() < this.expiresAt - SESSION_REFRESH_MARGIN_MS) {\n\t\t\treturn this.token;\n\t\t}\n\t\t// Single-flight the exchange: concurrent requests share one round-trip.\n\t\tthis.inflight ??= this.exchange().finally(() => {\n\t\t\tthis.inflight = null;\n\t\t});\n\t\treturn this.inflight;\n\t}\n\n\tinvalidate(): void {\n\t\tthis.token = null;\n\t\tthis.expiresAt = 0;\n\t}\n\n\tprivate async exchange(): Promise<string> {\n\t\tconst doFetch = this.config.fetch ?? fetch;\n\t\tconst turnstileToken = await this.turnstileToken();\n\t\tconst response = await doFetch(\n\t\t\t`${this.config.baseUrl.replace(/\\/$/, '')}/api/v1/public/calls/session`,\n\t\t\t{\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tpublishableKey: this.config.publishableKey,\n\t\t\t\t\t...(turnstileToken !== undefined ? { turnstileToken } : {}),\n\t\t\t\t}),\n\t\t\t}\n\t\t);\n\t\tconst parsed: unknown = await response.json().catch(() => null);\n\t\tif (!response.ok) {\n\t\t\tthrow new SignalingError(\n\t\t\t\tresponse.status,\n\t\t\t\t'Session exchange failed — check the publishable key and that this origin is on its allowlist',\n\t\t\t\tparsed\n\t\t\t);\n\t\t}\n\t\tconst session = parsed as PublicSessionResponse;\n\t\tthis.token = session.token;\n\t\tthis.expiresAt = Date.now() + session.expiresIn * 1_000;\n\t\tthis.flowIdValue = session.flowId;\n\t\treturn session.token;\n\t}\n}\n\n/**\n * Thin HTTP client for the Talkif WebRTC signaling routes. Two modes:\n * - authenticated (dashboard/API): `/api/v1/accounts/{accountId}/calls/webrtc`\n * - public (embed): `/api/v1/public/calls`, session token minted from the\n * publishable key and refreshed transparently.\n */\nexport class SignalingClient {\n\tprivate readonly config: TalkifConfig;\n\treadonly session: PublicSession | null;\n\n\tconstructor(config: TalkifConfig) {\n\t\tthis.config = config;\n\t\tthis.session = isPublicConfig(config) ? new PublicSession(config) : null;\n\t}\n\n\tget isPublic(): boolean {\n\t\treturn this.session !== null;\n\t}\n\n\tprivate get base(): string {\n\t\tconst baseUrl = this.config.baseUrl.replace(/\\/$/, '');\n\t\tif (isPublicConfig(this.config)) {\n\t\t\treturn `${baseUrl}/api/v1/public/calls`;\n\t\t}\n\t\treturn `${baseUrl}/api/v1/accounts/${this.config.accountId}/calls/webrtc`;\n\t}\n\n\tprivate async authorization(): Promise<string> {\n\t\tif (this.session) {\n\t\t\treturn `Bearer ${await this.session.getToken()}`;\n\t\t}\n\t\treturn (this.config as { auth: () => string | Promise<string> }).auth();\n\t}\n\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST',\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\tretriedAuth = false\n\t): Promise<T> {\n\t\tconst doFetch = this.config.fetch ?? fetch;\n\t\tconst authorization = await this.authorization();\n\t\tconst response = await doFetch(`${this.base}${path}`, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\tauthorization,\n\t\t\t\t...(body !== undefined ? { 'content-type': 'application/json' } : {}),\n\t\t\t},\n\t\t\t...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n\t\t});\n\n\t\t// Public mode: an expired/rotated session token gets one transparent\n\t\t// re-exchange + retry before surfacing the failure.\n\t\tif (response.status === 401 && this.session && !retriedAuth) {\n\t\t\tthis.session.invalidate();\n\t\t\treturn this.request(method, path, body, true);\n\t\t}\n\n\t\tlet parsed: unknown = null;\n\t\tconst text = await response.text();\n\t\tif (text) {\n\t\t\ttry {\n\t\t\t\tparsed = JSON.parse(text);\n\t\t\t} catch {\n\t\t\t\tparsed = text;\n\t\t\t}\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst message =\n\t\t\t\ttypeof parsed === 'object' && parsed !== null && 'detail' in parsed\n\t\t\t\t\t? String((parsed as { detail: unknown }).detail)\n\t\t\t\t\t: typeof parsed === 'object' && parsed !== null && 'message' in parsed\n\t\t\t\t\t\t? String((parsed as { message: unknown }).message)\n\t\t\t\t\t\t: `Signaling request failed: ${method} ${path} → ${response.status}`;\n\t\t\tthrow new SignalingError(response.status, message, parsed);\n\t\t}\n\t\treturn parsed as T;\n\t}\n\n\tcreateCall(body: CreateCallRequest): Promise<CreateCallResponse> {\n\t\tif (this.isPublic) {\n\t\t\t// Flow is bound to the publishable key server-side; body is empty.\n\t\t\treturn this.request('POST', '/calls', {});\n\t\t}\n\t\treturn this.request('POST', '', body);\n\t}\n\n\tcreateTestCall(body: CreateTestCallRequest): Promise<CreateCallResponse> {\n\t\tif (this.isPublic) {\n\t\t\tthrow new TalkifCallError(\n\t\t\t\t'signaling-failed',\n\t\t\t\t'Draft test calls are not available on the public surface'\n\t\t\t);\n\t\t}\n\t\treturn this.request('POST', '/test', body);\n\t}\n\n\tgetIceServers(): Promise<IceServersResponse> {\n\t\treturn this.request('GET', '/ice-servers');\n\t}\n\n\tsendOffer(callId: string, body: OfferRequest): Promise<OfferResponse> {\n\t\tconst path = this.isPublic ? `/calls/${callId}/offer` : `/${callId}/offer`;\n\t\treturn this.request('POST', path, body);\n\t}\n\n\tgetCall(callId: string): Promise<WebRTCCall> {\n\t\tconst path = this.isPublic ? `/calls/${callId}` : `/${callId}`;\n\t\treturn this.request('GET', path);\n\t}\n}\n\n/** Map a signaling failure to the library's typed error. */\nexport function asSignalingCallError(error: unknown): TalkifCallError {\n\tif (error instanceof TalkifCallError) return error;\n\tif (error instanceof SignalingError) {\n\t\tconst code =\n\t\t\terror.status === 401\n\t\t\t\t? 'session-rejected'\n\t\t\t\t: error.status === 409\n\t\t\t\t\t? 'call-active'\n\t\t\t\t\t: error.status === 429\n\t\t\t\t\t\t? 'rate-limited'\n\t\t\t\t\t\t: error.status === 503\n\t\t\t\t\t\t\t? 'no-agent'\n\t\t\t\t\t\t\t: 'signaling-failed';\n\t\treturn new TalkifCallError(code, error.message, error);\n\t}\n\tconst message = error instanceof Error ? error.message : 'Signaling request failed';\n\treturn new TalkifCallError('signaling-failed', message, error);\n}\n","import { DataChannel } from './data-channel.js';\nimport { Emitter } from './emitter.js';\nimport { CallEventsClient } from './events.js';\nimport { asSignalingCallError, SignalingClient } from './signaling.js';\nimport type {\n\tAppMessage,\n\tCallEndReason,\n\tCallEventEnvelope,\n\tCallState,\n\tIceServer,\n\tSignallingPayload,\n\tStartCallOptions,\n\tTalkifCallEventMap,\n\tTalkifConfig,\n} from './types.js';\nimport { TalkifCallError, TERMINAL_CALL_STATUSES } from './types.js';\n\n// If no external health confirmation (SSE/app message) arrives within this\n// window after media connects, verify call health against the backend over\n// REST before tearing down. Silence alone is NOT proof of pipeline death.\nconst PIPELINE_HEALTH_TIMEOUT_MS = 15_000;\n\n// Safety net for ICE gathering: send whatever we have if no relay candidate\n// shows up. With iceTransportPolicy 'relay' the first relay(udp) candidate is\n// all the bot needs; TURN tcp/443 allocations routinely never complete, so\n// waiting for iceGatheringState === 'complete' would stall every call.\nconst ICE_FIRST_RELAY_TIMEOUT_MS = 1_500;\n\n/**\n * One WebRTC voice session with a Talkif agent.\n *\n * Lifecycle: `idle → requesting → connecting → connected → ended | error`.\n * A TalkifCall instance is single-use: create a new one per call.\n */\nexport class TalkifCall {\n\tprivate readonly signaling: SignalingClient;\n\tprivate readonly config: TalkifConfig;\n\tprivate readonly emitter = new Emitter<TalkifCallEventMap>();\n\tprivate events: CallEventsClient | null = null;\n\n\tprivate pc: RTCPeerConnection | null = null;\n\tprivate localStream: MediaStream | null = null;\n\tprivate remoteAudio: HTMLAudioElement | null = null;\n\tprivate ownsAudioElement = false;\n\tprivate dataChannel: DataChannel | null = null;\n\tprivate iceServers: IceServer[] = [];\n\n\tprivate durationTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate healthTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate pipelineConfirmed = false;\n\tprivate renegotiating = false;\n\n\tprivate _state: CallState = 'idle';\n\tprivate _callId: string | null = null;\n\tprivate _botId: string | null = null;\n\tprivate _muted = false;\n\tprivate _durationSecs = 0;\n\n\tconstructor(config: TalkifConfig) {\n\t\tthis.config = config;\n\t\tthis.signaling = new SignalingClient(config);\n\t}\n\n\t// -- public surface -------------------------------------------------------\n\n\tget state(): CallState {\n\t\treturn this._state;\n\t}\n\n\tget callId(): string | null {\n\t\treturn this._callId;\n\t}\n\n\tget botId(): string | null {\n\t\treturn this._botId;\n\t}\n\n\tget muted(): boolean {\n\t\treturn this._muted;\n\t}\n\n\tget durationSecs(): number {\n\t\treturn this._durationSecs;\n\t}\n\n\ton<K extends keyof TalkifCallEventMap>(\n\t\tevent: K,\n\t\tlistener: (payload: TalkifCallEventMap[K]) => void\n\t): () => void {\n\t\treturn this.emitter.on(event, listener);\n\t}\n\n\toff<K extends keyof TalkifCallEventMap>(\n\t\tevent: K,\n\t\tlistener: (payload: TalkifCallEventMap[K]) => void\n\t): void {\n\t\tthis.emitter.off(event, listener);\n\t}\n\n\t/**\n\t * External health signal (e.g. the dashboard's SSE layer saw a\n\t * RUNNING/CONNECTED status). Cancels the REST health watchdog.\n\t */\n\tconfirmPipelineHealthy(): void {\n\t\tthis.pipelineConfirmed = true;\n\t\tif (this.healthTimer) {\n\t\t\tclearTimeout(this.healthTimer);\n\t\t\tthis.healthTimer = null;\n\t\t}\n\t}\n\n\t/**\n\t * External terminal signal (e.g. SSE reported COMPLETED). Tears down and\n\t * transitions to 'ended'.\n\t */\n\tendedExternally(): void {\n\t\tif (this._state !== 'connecting' && this._state !== 'connected') return;\n\t\tthis.teardown();\n\t\tthis.setState('ended');\n\t\tthis.emitter.emit('ended', { reason: 'external' });\n\t}\n\n\t/** Send an arbitrary JSON app message to the bot over the data channel. */\n\tsendAppMessage(message: AppMessage): boolean {\n\t\treturn this.dataChannel?.sendAppMessage(message) ?? false;\n\t}\n\n\tsetMuted(muted: boolean): void {\n\t\tif (!this.localStream) return;\n\t\tfor (const track of this.localStream.getAudioTracks()) {\n\t\t\ttrack.enabled = !muted;\n\t\t}\n\t\tthis._muted = muted;\n\t\tthis.emitter.emit('mutechange', { muted });\n\t}\n\n\t/** User-initiated hangup. */\n\thangup(): void {\n\t\tif (this._state === 'idle' || this._state === 'ended' || this._state === 'error') return;\n\t\tthis.teardown();\n\t\tthis.setState('ended');\n\t\tthis.emitter.emit('ended', { reason: 'local-hangup' });\n\t}\n\n\t/** Full teardown regardless of state — safe to call multiple times. */\n\tdispose(): void {\n\t\tthis.teardown();\n\t\tthis.signaling.session?.dispose();\n\t\tthis.emitter.removeAll();\n\t}\n\n\t// -- lifecycle ------------------------------------------------------------\n\n\tasync start(options: StartCallOptions): Promise<void> {\n\t\tif (this._state !== 'idle') {\n\t\t\tthrow new TalkifCallError('signaling-failed', `Cannot start a call from state '${this._state}' — create a new TalkifCall`);\n\t\t}\n\t\tthis.setState('requesting');\n\n\t\tif (!this.signaling.isPublic && !options.flowId) {\n\t\t\tthrow new TalkifCallError('signaling-failed', 'flowId is required in authenticated mode');\n\t\t}\n\n\t\ttry {\n\t\t\t// Run bot warmup (create call → bot assignment, 2-5s) in parallel with\n\t\t\t// local setup (ICE fetch, mic, offer, candidate gathering).\n\t\t\tconst flowId = options.flowId ?? '';\n\t\t\tconst [callResponse, local] = await Promise.all([\n\t\t\t\toptions.draftDefinition !== undefined\n\t\t\t\t\t? this.signaling.createTestCall({\n\t\t\t\t\t\t\tflowId,\n\t\t\t\t\t\t\tdefinition: options.draftDefinition,\n\t\t\t\t\t\t\t...(options.metadata !== undefined ? { metadata: options.metadata } : {}),\n\t\t\t\t\t\t})\n\t\t\t\t\t: this.signaling.createCall({\n\t\t\t\t\t\t\tflowId,\n\t\t\t\t\t\t\t...(options.metadata !== undefined ? { metadata: options.metadata } : {}),\n\t\t\t\t\t\t}),\n\t\t\t\tthis.prepareLocal(options),\n\t\t\t]);\n\n\t\t\tthis._callId = callResponse.callId;\n\t\t\tthis.pc = local.pc;\n\t\t\tthis.localStream = local.stream;\n\t\t\tthis.setState('connecting');\n\n\t\t\tthis.pc.ontrack = (event) => {\n\t\t\t\tconst stream = event.streams[0];\n\t\t\t\tif (!stream) return;\n\t\t\t\tthis.attachRemoteAudio(stream, options);\n\t\t\t\tthis.emitter.emit('track', { stream });\n\t\t\t};\n\n\t\t\tthis.pc.onconnectionstatechange = () => {\n\t\t\t\tconst state = this.pc?.connectionState;\n\t\t\t\tif (state === 'failed' || state === 'disconnected') {\n\t\t\t\t\tthis.fail(new TalkifCallError('connection-failed', 'WebRTC connection failed'));\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tthis.dataChannel = new DataChannel(this.pc, {\n\t\t\t\tonSignalling: (message) => this.handleSignalling(message),\n\t\t\t\tonAppMessage: (message) => this.emitter.emit('appmessage', { message }),\n\t\t\t});\n\n\t\t\tconst answer = await this.signaling.sendOffer(callResponse.callId, {\n\t\t\t\tsdp: this.mustLocalSdp(),\n\t\t\t\ticeServers: this.iceServers,\n\t\t\t});\n\t\t\tawait this.pc.setRemoteDescription({ type: 'answer', sdp: answer.sdp });\n\t\t\tthis._botId = answer.botId ?? null;\n\n\t\t\tthis.setState('connected');\n\t\t\tthis.emitter.emit('connected', { callId: callResponse.callId, botId: this._botId });\n\t\t\tthis.startDurationTimer();\n\t\t\tthis.armHealthWatchdog(callResponse.callId);\n\t\t\tthis.connectEvents();\n\t\t} catch (error) {\n\t\t\tthis.fail(\n\t\t\t\terror instanceof TalkifCallError ? error : asSignalingCallError(error)\n\t\t\t);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate async prepareLocal(options: StartCallOptions): Promise<{ pc: RTCPeerConnection; stream: MediaStream }> {\n\t\tconst { iceServers } = await this.signaling.getIceServers();\n\t\tthis.iceServers = iceServers;\n\n\t\tif (!navigator.mediaDevices?.getUserMedia) {\n\t\t\tthrow new TalkifCallError('media-unavailable', 'Microphone access unavailable. This requires HTTPS or localhost.');\n\t\t}\n\t\tconst stream = await navigator.mediaDevices\n\t\t\t.getUserMedia({ audio: options.audio ?? true })\n\t\t\t.catch((cause: unknown) => {\n\t\t\t\tthrow new TalkifCallError('media-unavailable', 'Microphone permission denied or unavailable', cause);\n\t\t\t});\n\n\t\t// Relay-only transport: skips host/srflx gathering (no dead-interface\n\t\t// timeouts), works through firewalls that block UDP, avoids TURN\n\t\t// \"Forbidden IP\" errors for private IPs, and gathers in ~100-200ms.\n\t\tconst pc = new RTCPeerConnection({\n\t\t\ticeServers: iceServers.map((s) => ({\n\t\t\t\turls: s.urls,\n\t\t\t\t...(s.username !== undefined ? { username: s.username } : {}),\n\t\t\t\t...(s.credential !== undefined ? { credential: s.credential } : {}),\n\t\t\t})),\n\t\t\ticeTransportPolicy: 'relay',\n\t\t});\n\t\tfor (const track of stream.getTracks()) {\n\t\t\tpc.addTrack(track, stream);\n\t\t}\n\n\t\tconst offer = await pc.createOffer();\n\t\tawait pc.setLocalDescription(offer);\n\t\tif (!offer.sdp) {\n\t\t\tthrow new TalkifCallError('signaling-failed', 'Failed to create SDP offer');\n\t\t}\n\n\t\tawait this.waitForFirstRelayCandidate(pc);\n\t\treturn { pc, stream };\n\t}\n\n\tprivate waitForFirstRelayCandidate(pc: RTCPeerConnection): Promise<void> {\n\t\treturn new Promise<void>((resolve) => {\n\t\t\tif (pc.iceGatheringState === 'complete') {\n\t\t\t\tresolve();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet settled = false;\n\t\t\tconst finish = () => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tpc.removeEventListener('icecandidate', onCandidate);\n\t\t\t\tpc.removeEventListener('icegatheringstatechange', onGatheringComplete);\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tconst timeout = setTimeout(finish, ICE_FIRST_RELAY_TIMEOUT_MS);\n\t\t\tconst onCandidate = (e: RTCPeerConnectionIceEvent) => {\n\t\t\t\tif (e.candidate && e.candidate.type === 'relay') finish();\n\t\t\t};\n\t\t\tconst onGatheringComplete = () => {\n\t\t\t\tif (pc.iceGatheringState === 'complete') finish();\n\t\t\t};\n\t\t\tpc.addEventListener('icecandidate', onCandidate);\n\t\t\tpc.addEventListener('icegatheringstatechange', onGatheringComplete);\n\t\t});\n\t}\n\n\tprivate attachRemoteAudio(stream: MediaStream, options: StartCallOptions): void {\n\t\tif (options.audioElement === null) return; // consumer opted out\n\t\tif (!this.remoteAudio) {\n\t\t\tif (options.audioElement) {\n\t\t\t\tthis.remoteAudio = options.audioElement;\n\t\t\t\tthis.ownsAudioElement = false;\n\t\t\t} else {\n\t\t\t\tthis.remoteAudio = new Audio();\n\t\t\t\tthis.remoteAudio.autoplay = true;\n\t\t\t\tthis.ownsAudioElement = true;\n\t\t\t}\n\t\t}\n\t\tthis.remoteAudio.srcObject = stream;\n\t}\n\n\t// -- data-channel protocol ------------------------------------------------\n\n\tprivate handleSignalling(message: SignallingPayload): void {\n\t\tswitch (message.type) {\n\t\t\tcase 'peerLeft':\n\t\t\t\tif (this._state === 'connected' || this._state === 'connecting') {\n\t\t\t\t\tthis.teardown();\n\t\t\t\t\tthis.setState('ended');\n\t\t\t\t\tthis.emitter.emit('ended', { reason: 'peer-left' });\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'renegotiate':\n\t\t\t\tvoid this.renegotiate();\n\t\t\t\tbreak;\n\t\t\tcase 'trackStatus':\n\t\t\t\t// Bot→client trackStatus is not part of the current protocol; ignore.\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Bot asked for a new offer/answer cycle. Re-offer through the same relay\n\t * endpoint. If the backend rejects re-offers (older deployments), the call\n\t * keeps running on the existing session — surfaced via `renegotiated`.\n\t */\n\tprivate async renegotiate(): Promise<void> {\n\t\tif (!this.pc || !this._callId || this.renegotiating) return;\n\t\tthis.renegotiating = true;\n\t\ttry {\n\t\t\tconst offer = await this.pc.createOffer({ iceRestart: true });\n\t\t\tawait this.pc.setLocalDescription(offer);\n\t\t\tawait this.waitForFirstRelayCandidate(this.pc);\n\t\t\tconst answer = await this.signaling.sendOffer(this._callId, {\n\t\t\t\tsdp: this.mustLocalSdp(),\n\t\t\t\ticeServers: this.iceServers,\n\t\t\t});\n\t\t\tawait this.pc.setRemoteDescription({ type: 'answer', sdp: answer.sdp });\n\t\t\tthis.emitter.emit('renegotiated', { ok: true });\n\t\t} catch {\n\t\t\tthis.emitter.emit('renegotiated', { ok: false });\n\t\t} finally {\n\t\t\tthis.renegotiating = false;\n\t\t}\n\t}\n\n\t// -- realtime events (public mode) ----------------------------------------\n\n\t/**\n\t * Public mode only: open the realtime events WebSocket for this call.\n\t * Events double as the pipeline-health signal, and a terminal `status`\n\t * event ends the call from the server side (e.g. max-duration reached).\n\t */\n\tprivate connectEvents(): void {\n\t\tconst session = this.signaling.session;\n\t\tif (!session) return; // authenticated mode: dashboard SSE covers events\n\n\t\tthis.events = new CallEventsClient({\n\t\t\tbaseUrl: this.config.baseUrl,\n\t\t\tgetToken: () => session.getToken(),\n\t\t\tonEvent: (event) => this.handleCallEvent(event),\n\t\t});\n\t\tthis.events.connect();\n\t}\n\n\tprivate handleCallEvent(event: CallEventEnvelope): void {\n\t\t// Events flowing at all = the bot pipeline is alive.\n\t\tthis.confirmPipelineHealthy();\n\t\tthis.emitter.emit('callevent', { event });\n\n\t\tswitch (event.type) {\n\t\t\tcase 'transcript': {\n\t\t\t\tconst role = typeof event.data.role === 'string' ? event.data.role : 'unknown';\n\t\t\t\tconst content = typeof event.data.content === 'string' ? event.data.content : '';\n\t\t\t\tthis.emitter.emit('transcript', { role, content, data: event.data });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'interim':\n\t\t\t\tthis.emitter.emit('interim', { data: event.data });\n\t\t\t\tbreak;\n\t\t\tcase 'tts': {\n\t\t\t\tconst text = typeof event.data.text === 'string' ? event.data.text : '';\n\t\t\t\tif (!text) break;\n\t\t\t\tconst timestamp = typeof event.data.timestamp === 'number' ? event.data.timestamp : 0;\n\t\t\t\tthis.emitter.emit('ttschunk', { text, timestamp, data: event.data });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'tts_word': {\n\t\t\t\tconst word = typeof event.data.word === 'string' ? event.data.word : '';\n\t\t\t\tif (!word) break;\n\t\t\t\tconst ptsMs = typeof event.data.pts_ms === 'number' ? event.data.pts_ms : null;\n\t\t\t\tconst timestamp = typeof event.data.timestamp === 'number' ? event.data.timestamp : 0;\n\t\t\t\tthis.emitter.emit('ttsword', { word, ptsMs, timestamp, data: event.data });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'status': {\n\t\t\t\tconst status = typeof event.data.status === 'string' ? event.data.status : '';\n\t\t\t\tif (TERMINAL_CALL_STATUSES.has(status.toUpperCase() as never)) {\n\t\t\t\t\tthis.endedExternally();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// -- health watchdog ------------------------------------------------------\n\n\tprivate armHealthWatchdog(callId: string): void {\n\t\tthis.pipelineConfirmed = false;\n\t\tthis.healthTimer = setTimeout(() => {\n\t\t\tthis.healthTimer = null;\n\t\t\tif (this.pipelineConfirmed) return;\n\t\t\tvoid this.verifyPipelineHealth(callId);\n\t\t}, PIPELINE_HEALTH_TIMEOUT_MS);\n\t}\n\n\tprivate async verifyPipelineHealth(callId: string): Promise<void> {\n\t\ttry {\n\t\t\tconst call = await this.signaling.getCall(callId);\n\t\t\tif (this.pipelineConfirmed || !this.pc) return;\n\t\t\tif (TERMINAL_CALL_STATUSES.has(call.status)) {\n\t\t\t\tthis.fail(new TalkifCallError('pipeline-dead', `Backend reports terminal status '${call.status}'`));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Backend says the call is alive — the silence was the event layer's\n\t\t\t// fault, not the pipeline's.\n\t\t\tthis.pipelineConfirmed = true;\n\t\t} catch {\n\t\t\tif (this.pipelineConfirmed || !this.pc) return;\n\t\t\t// REST unreachable — fall back to the media-plane signal. Flowing\n\t\t\t// audio means alive; later media death is caught by\n\t\t\t// onconnectionstatechange. Kill only when both signals are gone.\n\t\t\tif (this.pc.connectionState === 'connected') {\n\t\t\t\tthis.pipelineConfirmed = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.fail(new TalkifCallError('pipeline-dead', 'No health signal, REST unreachable, media not connected'));\n\t\t}\n\t}\n\n\t// -- internals ------------------------------------------------------------\n\n\tprivate mustLocalSdp(): string {\n\t\tconst sdp = this.pc?.localDescription?.sdp;\n\t\tif (!sdp) {\n\t\t\tthrow new TalkifCallError('signaling-failed', 'Local SDP missing');\n\t\t}\n\t\treturn sdp;\n\t}\n\n\tprivate startDurationTimer(): void {\n\t\tthis._durationSecs = 0;\n\t\tthis.durationTimer = setInterval(() => {\n\t\t\tthis._durationSecs += 1;\n\t\t\tthis.emitter.emit('tick', { durationSecs: this._durationSecs });\n\t\t}, 1_000);\n\t}\n\n\tprivate fail(error: TalkifCallError): void {\n\t\tif (this._state === 'ended' || this._state === 'error') return;\n\t\tthis.teardown();\n\t\tthis.setState('error');\n\t\tthis.emitter.emit('error', { error });\n\t}\n\n\tprivate setState(state: CallState): void {\n\t\tconst previous = this._state;\n\t\tif (previous === state) return;\n\t\tthis._state = state;\n\t\tthis.emitter.emit('statechange', { state, previous });\n\t}\n\n\tprivate teardown(): void {\n\t\tthis.events?.close();\n\t\tthis.events = null;\n\n\t\tif (this.healthTimer) {\n\t\t\tclearTimeout(this.healthTimer);\n\t\t\tthis.healthTimer = null;\n\t\t}\n\t\tthis.pipelineConfirmed = false;\n\n\t\tif (this.durationTimer) {\n\t\t\tclearInterval(this.durationTimer);\n\t\t\tthis.durationTimer = null;\n\t\t}\n\n\t\tthis.dataChannel?.close();\n\t\tthis.dataChannel = null;\n\n\t\tif (this.localStream) {\n\t\t\tfor (const track of this.localStream.getTracks()) track.stop();\n\t\t\tthis.localStream = null;\n\t\t}\n\n\t\tif (this.remoteAudio) {\n\t\t\tthis.remoteAudio.pause();\n\t\t\tthis.remoteAudio.srcObject = null;\n\t\t\tif (this.ownsAudioElement) this.remoteAudio = null;\n\t\t}\n\n\t\tif (this.pc) {\n\t\t\tthis.pc.ontrack = null;\n\t\t\tthis.pc.onconnectionstatechange = null;\n\t\t\tthis.pc.close();\n\t\t\tthis.pc = null;\n\t\t}\n\t}\n}\n\nexport type { CallEndReason };\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/data-channel.ts","../src/emitter.ts","../src/events.ts","../src/types.ts","../src/turnstile.ts","../src/signaling.ts","../src/call.ts"],"sourcesContent":["import type { AppMessage, SignallingPayload, TrackStatusMessage } from './types.js';\n\nconst KEEPALIVE_INTERVAL_MS = 2_000;\n\nexport interface DataChannelHandlers {\n\tonSignalling: (message: SignallingPayload) => void;\n\tonAppMessage: (message: AppMessage) => void;\n}\n\n/**\n * Client side of the pipecat SmallWebRTC data-channel protocol:\n *\n * - `\"ping\"` strings every 2s keep the bot's liveness check happy (the bot\n * treats a connection as active when the last ping is <3s old, with the\n * ICE connection state as fallback).\n * - Inbound JSON is either `{type: \"signalling\", message: {...}}` (protocol)\n * or an arbitrary app message (forwarded to the consumer).\n */\nexport class DataChannel {\n\tprivate channel: RTCDataChannel;\n\tprivate keepalive: ReturnType<typeof setInterval> | null = null;\n\tprivate readonly handlers: DataChannelHandlers;\n\n\tconstructor(pc: RTCPeerConnection, handlers: DataChannelHandlers) {\n\t\tthis.handlers = handlers;\n\t\tthis.channel = pc.createDataChannel('pipecat', { ordered: true });\n\t\tthis.channel.onopen = () => this.startKeepalive();\n\t\tthis.channel.onclose = () => this.stopKeepalive();\n\t\tthis.channel.onmessage = (event) => this.handleMessage(event.data);\n\t}\n\n\tprivate startKeepalive(): void {\n\t\tthis.stopKeepalive();\n\t\tthis.keepalive = setInterval(() => {\n\t\t\tif (this.channel.readyState === 'open') {\n\t\t\t\tthis.channel.send('ping');\n\t\t\t}\n\t\t}, KEEPALIVE_INTERVAL_MS);\n\t}\n\n\tprivate stopKeepalive(): void {\n\t\tif (this.keepalive) {\n\t\t\tclearInterval(this.keepalive);\n\t\t\tthis.keepalive = null;\n\t\t}\n\t}\n\n\tprivate handleMessage(data: unknown): void {\n\t\tif (typeof data !== 'string') return;\n\t\tlet parsed: unknown;\n\t\ttry {\n\t\t\tparsed = JSON.parse(data);\n\t\t} catch {\n\t\t\treturn; // Non-JSON payloads (e.g. bot-side pings) carry no protocol meaning here.\n\t\t}\n\t\tif (typeof parsed !== 'object' || parsed === null) return;\n\n\t\tconst record = parsed as Record<string, unknown>;\n\t\tif (record['type'] === 'signalling' && typeof record['message'] === 'object' && record['message'] !== null) {\n\t\t\tthis.handlers.onSignalling(record['message'] as SignallingPayload);\n\t\t\treturn;\n\t\t}\n\t\tthis.handlers.onAppMessage(record as AppMessage);\n\t}\n\n\t/** Send an arbitrary JSON app message to the bot pipeline. */\n\tsendAppMessage(message: AppMessage): boolean {\n\t\tif (this.channel.readyState !== 'open') return false;\n\t\tthis.channel.send(JSON.stringify(message));\n\t\treturn true;\n\t}\n\n\t/** Tell the bot to enable/disable one of our media receivers. */\n\tsendTrackStatus(message: Omit<TrackStatusMessage, 'type'>): boolean {\n\t\tif (this.channel.readyState !== 'open') return false;\n\t\tthis.channel.send(JSON.stringify({ type: 'signalling', message: { type: 'trackStatus', ...message } }));\n\t\treturn true;\n\t}\n\n\tclose(): void {\n\t\tthis.stopKeepalive();\n\t\tthis.channel.onopen = null;\n\t\tthis.channel.onclose = null;\n\t\tthis.channel.onmessage = null;\n\t\tif (this.channel.readyState === 'open' || this.channel.readyState === 'connecting') {\n\t\t\tthis.channel.close();\n\t\t}\n\t}\n}\n","/** Minimal typed event emitter — no dependency, browser-safe. */\nexport class Emitter<Events extends object> {\n\tprivate listeners = new Map<keyof Events, Set<(payload: never) => void>>();\n\n\ton<K extends keyof Events>(event: K, listener: (payload: Events[K]) => void): () => void {\n\t\tlet set = this.listeners.get(event);\n\t\tif (!set) {\n\t\t\tset = new Set();\n\t\t\tthis.listeners.set(event, set);\n\t\t}\n\t\tset.add(listener as (payload: never) => void);\n\t\treturn () => this.off(event, listener);\n\t}\n\n\toff<K extends keyof Events>(event: K, listener: (payload: Events[K]) => void): void {\n\t\tthis.listeners.get(event)?.delete(listener as (payload: never) => void);\n\t}\n\n\temit<K extends keyof Events>(event: K, payload: Events[K]): void {\n\t\tconst set = this.listeners.get(event);\n\t\tif (!set) return;\n\t\tfor (const listener of [...set]) {\n\t\t\t(listener as (payload: Events[K]) => void)(payload);\n\t\t}\n\t}\n\n\tremoveAll(): void {\n\t\tthis.listeners.clear();\n\t}\n}\n","import type { CallEventEnvelope } from './types.js';\n\n/** Reconnect backoff schedule (ms). Caps at the last entry. */\nconst BACKOFF_MS = [500, 1_000, 2_000, 4_000, 8_000];\n\nexport interface CallEventsOptions {\n\t/** API origin, e.g. `https://api.talkif.ai`. */\n\tbaseUrl: string;\n\t/** Fresh session token per (re)connect — first-message auth. */\n\tgetToken: () => Promise<string>;\n\t/** Every delivered envelope for the session's call. */\n\tonEvent: (event: CallEventEnvelope) => void;\n\t/**\n\t * A `seq` gap was observed: this connection shed frames under backpressure.\n\t * The client auto-requests a server-side replay on reconnect; consumers that\n\t * render transcripts incrementally may want to clear-and-rebuild on replay.\n\t */\n\tonGap?: (expected: number, received: number) => void;\n\t/** Optional WebSocket constructor override (tests, Node). */\n\twebSocket?: typeof WebSocket;\n}\n\n/**\n * Client for the public realtime events WebSocket\n * (`/api/v1/public/ws/events`).\n *\n * Protocol: connect → send `{\"type\":\"auth\",\"token\":...}` within 5s → send\n * `{\"type\":\"subscribe\"}` (the session is auto-bound to its one call) →\n * receive `{v, type, call_id?, seq, ts, data}` envelopes. Reconnects with\n * backoff; after the first successful connection, reconnect subscribes with\n * `replay: true` to recover anything missed during the gap.\n */\nexport class CallEventsClient {\n\tprivate readonly options: CallEventsOptions;\n\tprivate ws: WebSocket | null = null;\n\tprivate closed = false;\n\tprivate attempts = 0;\n\tprivate lastSeq = 0;\n\tprivate reconnectTimer: ReturnType<typeof setTimeout> | null = null;\n\n\tconstructor(options: CallEventsOptions) {\n\t\tthis.options = options;\n\t}\n\n\tconnect(): void {\n\t\tif (this.closed || this.ws) return;\n\t\tvoid this.open();\n\t}\n\n\tclose(): void {\n\t\tthis.closed = true;\n\t\tif (this.reconnectTimer) {\n\t\t\tclearTimeout(this.reconnectTimer);\n\t\t\tthis.reconnectTimer = null;\n\t\t}\n\t\tthis.ws?.close(1000);\n\t\tthis.ws = null;\n\t}\n\n\tprivate wsUrl(): string {\n\t\tconst base = this.options.baseUrl.replace(/\\/$/, '').replace(/^http/, 'ws');\n\t\treturn `${base}/api/v1/public/ws/events`;\n\t}\n\n\tprivate async open(): Promise<void> {\n\t\tlet token: string;\n\t\ttry {\n\t\t\ttoken = await this.options.getToken();\n\t\t} catch {\n\t\t\tthis.scheduleReconnect();\n\t\t\treturn;\n\t\t}\n\t\tif (this.closed) return;\n\n\t\tconst WS = this.options.webSocket ?? WebSocket;\n\t\tconst ws = new WS(this.wsUrl());\n\t\tthis.ws = ws;\n\n\t\tws.onopen = () => {\n\t\t\tws.send(JSON.stringify({ type: 'auth', token }));\n\t\t\t// Replay recovers anything emitted before this subscribe landed\n\t\t\t// (the opening word) and anything shed during a reconnect gap.\n\t\t\tws.send(JSON.stringify({ type: 'subscribe', replay: true }));\n\t\t};\n\n\t\tws.onmessage = (message: MessageEvent) => {\n\t\t\tif (typeof message.data !== 'string') return;\n\t\t\tlet envelope: CallEventEnvelope;\n\t\t\ttry {\n\t\t\t\tenvelope = JSON.parse(message.data) as CallEventEnvelope;\n\t\t\t} catch {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (typeof envelope.type !== 'string' || typeof envelope.seq !== 'number') return;\n\n\t\t\tif (envelope.type === 'subscribed') {\n\t\t\t\tthis.attempts = 0;\n\t\t\t\tthis.lastSeq = envelope.seq;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (envelope.type === 'pong' || envelope.type === 'unsubscribed') {\n\t\t\t\tthis.lastSeq = envelope.seq;\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (this.lastSeq > 0 && envelope.seq > this.lastSeq + 1) {\n\t\t\t\tthis.options.onGap?.(this.lastSeq + 1, envelope.seq);\n\t\t\t}\n\t\t\tthis.lastSeq = envelope.seq;\n\t\t\tthis.options.onEvent(envelope);\n\t\t};\n\n\t\tws.onclose = (event: CloseEvent) => {\n\t\t\tif (this.ws !== ws) return;\n\t\t\tthis.ws = null;\n\t\t\t// seq is per-connection — reset the tracker.\n\t\t\tthis.lastSeq = 0;\n\t\t\t// 4401/4403 are auth/scope rejections: retrying with the same session\n\t\t\t// can still succeed after a token refresh (getToken re-exchanges), but\n\t\t\t// a scope violation (4403) will never heal — stop there.\n\t\t\tif (event.code === 4403) {\n\t\t\t\tthis.closed = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.scheduleReconnect();\n\t\t};\n\n\t\tws.onerror = () => {\n\t\t\t// onclose follows and handles reconnection.\n\t\t};\n\t}\n\n\tprivate scheduleReconnect(): void {\n\t\tif (this.closed || this.reconnectTimer) return;\n\t\tconst delay = BACKOFF_MS[Math.min(this.attempts, BACKOFF_MS.length - 1)] ?? 8_000;\n\t\tthis.attempts += 1;\n\t\tthis.reconnectTimer = setTimeout(() => {\n\t\t\tthis.reconnectTimer = null;\n\t\t\tvoid this.open();\n\t\t}, delay);\n\t}\n}\n","/**\n * Wire types for the Talkif WebRTC signaling API and the pipecat SmallWebRTC\n * data-channel protocol spoken by Talkif voice bots.\n *\n * These shapes are the public contract. Field casing matches the backend JSON\n * exactly (camelCase over HTTP).\n */\n\n// ---------------------------------------------------------------------------\n// HTTP signaling\n// ---------------------------------------------------------------------------\n\nexport interface IceServer {\n\turls: string;\n\tusername?: string;\n\tcredential?: string;\n}\n\nexport interface IceServersResponse {\n\ticeServers: IceServer[];\n\t/** Seconds remaining on the underlying TURN credential. */\n\tttl: number;\n}\n\nexport interface CreateCallRequest {\n\tflowId: string;\n\tmetadata?: Record<string, unknown>;\n}\n\nexport interface CreateTestCallRequest {\n\tflowId: string;\n\t/**\n\t * Draft flow definition (frontend `FlowDefinition` shape). The library does\n\t * not interpret it — it is passed through to the backend, which validates\n\t * and compiles it for the bot.\n\t */\n\tdefinition: Record<string, unknown>;\n\tmetadata?: Record<string, unknown>;\n}\n\nexport type CallStatus =\n\t| 'CREATED'\n\t| 'BOT_ASSIGNED'\n\t| 'QUEUED'\n\t| 'RINGING'\n\t| 'IN_PROGRESS'\n\t| 'COMPLETED'\n\t| 'FAILED'\n\t| 'NO_ANSWER'\n\t| 'BUSY'\n\t| 'CANCELED';\n\n/** REST statuses that mean the call is genuinely over. */\nexport const TERMINAL_CALL_STATUSES: ReadonlySet<CallStatus> = new Set([\n\t'COMPLETED',\n\t'FAILED',\n\t'NO_ANSWER',\n\t'BUSY',\n\t'CANCELED',\n]);\n\nexport interface CreateCallResponse {\n\tcallId: string;\n\tflowId: string;\n\tstatus: CallStatus;\n\tbotId?: string | null;\n}\n\nexport interface WebRTCCall {\n\tcallId: string;\n\tstatus: CallStatus;\n}\n\nexport interface OfferRequest {\n\tsdp: string;\n\ticeServers?: IceServer[];\n}\n\n/** Public surface: token-exchange response (`POST /public/calls/session`). */\nexport interface PublicSessionResponse {\n\t/** Signed session token — sent as `Authorization: Bearer <token>`. */\n\ttoken: string;\n\t/** Seconds until the token expires (~15 min). */\n\texpiresIn: number;\n\t/** The flow this session may call (bound to the publishable key). */\n\tflowId: string;\n}\n\nexport interface OfferResponse {\n\tsdp: string;\n\tsdpType: string;\n\tbotId?: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Data-channel protocol (pipecat SmallWebRTC — bot side)\n// ---------------------------------------------------------------------------\n\n/** Bot → client: bot requests a new offer/answer cycle. */\nexport interface RenegotiateMessage {\n\ttype: 'renegotiate';\n}\n\n/** Bot → client: bot is closing the connection. */\nexport interface PeerLeftMessage {\n\ttype: 'peerLeft';\n}\n\n/** Client → bot: enable/disable a media receiver. 0=audio, 1=video, 2=screen. */\nexport interface TrackStatusMessage {\n\ttype: 'trackStatus';\n\treceiver_index: 0 | 1 | 2;\n\tenabled: boolean;\n}\n\nexport type SignallingPayload = RenegotiateMessage | PeerLeftMessage | TrackStatusMessage;\n\nexport interface SignallingEnvelope {\n\ttype: 'signalling';\n\tmessage: SignallingPayload;\n}\n\n/**\n * Anything on the data channel that is not the signalling envelope (and not a\n * `\"ping\"` keepalive string) is an app message: arbitrary JSON exchanged with\n * the bot pipeline.\n */\nexport type AppMessage = Record<string, unknown>;\n\n// ---------------------------------------------------------------------------\n// Realtime events WebSocket (`/api/v1/public/ws/events`)\n// ---------------------------------------------------------------------------\n\n/**\n * Server envelope on the realtime events WebSocket. `seq` is per-connection\n * and monotonic — a gap means THIS connection dropped frames under\n * backpressure, not that the call missed anything.\n */\nexport interface CallEventEnvelope {\n\tv: number;\n\ttype: string;\n\tcall_id?: string;\n\tseq: number;\n\tts: number;\n\tdata: Record<string, unknown>;\n}\n\n/** Event types the public WS delivers for the session's call. */\nexport type CallEventType =\n\t| 'transcript'\n\t| 'interim'\n\t| 'status'\n\t| 'tts'\n\t| 'tts_word'\n\t| 'speech'\n\t| 'turn'\n\t| 'node_transition'\n\t| 'error';\n\n// ---------------------------------------------------------------------------\n// Client state machine & events\n// ---------------------------------------------------------------------------\n\nexport type CallState =\n\t| 'idle'\n\t| 'requesting'\n\t| 'connecting'\n\t| 'connected'\n\t| 'ended'\n\t| 'error';\n\nexport interface TalkifCallEventMap {\n\t/** State machine transition. */\n\tstatechange: { state: CallState; previous: CallState };\n\t/** Media is flowing and the SDP answer is applied. */\n\tconnected: { callId: string; botId: string | null };\n\t/** Remote (bot) audio track arrived. */\n\ttrack: { stream: MediaStream };\n\t/** Call finished normally (user hangup, bot peerLeft, terminal status). */\n\tended: { reason: CallEndReason };\n\t/** Fatal failure. The call is torn down; state is 'error'. */\n\terror: { error: TalkifCallError };\n\t/** One-second duration tick while connected. */\n\ttick: { durationSecs: number };\n\t/** JSON app message from the bot over the data channel. */\n\tappmessage: { message: AppMessage };\n\t/** Bot asked for renegotiation. Emitted after the re-offer is attempted. */\n\trenegotiated: { ok: boolean };\n\t/** Mute toggled. */\n\tmutechange: { muted: boolean };\n\t/**\n\t * Any realtime event for this call from the events WebSocket (public mode).\n\t * Fires for every delivered envelope, including those with dedicated\n\t * events below.\n\t */\n\tcallevent: { event: CallEventEnvelope };\n\t/** Final transcript line (public mode, via the events WebSocket). */\n\ttranscript: { role: string; content: string; data: Record<string, unknown> };\n\t/** Interim (in-progress) transcription (public mode). May be dropped under backpressure. */\n\tinterim: { data: Record<string, unknown> };\n\t/**\n\t * Word-level TTS timing (public mode). Drives karaoke-style caption\n\t * highlighting; only words actually spoken are delivered. May be dropped\n\t * under backpressure.\n\t */\n\tttsword: TtsWordEvent;\n\t/**\n\t * Sentence-level chunk of the agent's reply (public mode), emitted when the\n\t * LLM output is handed to TTS synthesis — ahead of audio playback. Use for\n\t * chat-style streaming of the reply; reconcile with the final `transcript`\n\t * (on interruption the tail may never be spoken). May be dropped under\n\t * backpressure.\n\t */\n\tttschunk: TtsChunkEvent;\n}\n\n/** Payload of a `tts` realtime event. */\nexport interface TtsChunkEvent {\n\t/** Sentence-level text chunk of the agent's reply. */\n\ttext: string;\n\t/** Unix timestamp from the bot (seconds, fractional). */\n\ttimestamp: number;\n\t/** Full raw event payload (character counts, TTS timing metrics…). */\n\tdata: Record<string, unknown>;\n}\n\n/** Payload of a `tts_word` realtime event. */\nexport interface TtsWordEvent {\n\t/** The spoken word. */\n\tword: string;\n\t/** Presentation timestamp — when the word is spoken in the audio (ms), if known. */\n\tptsMs: number | null;\n\t/** Unix timestamp from the bot (seconds, fractional). */\n\ttimestamp: number;\n\t/** Full raw event payload. */\n\tdata: Record<string, unknown>;\n}\n\nexport type CallEndReason =\n\t| 'local-hangup'\n\t| 'peer-left'\n\t| 'terminal-status'\n\t| 'external';\n\nexport type TalkifCallErrorCode =\n\t| 'media-unavailable'\n\t| 'signaling-failed'\n\t| 'connection-failed'\n\t| 'pipeline-dead'\n\t| 'renegotiation-failed'\n\t/** Publishable key rejected, origin not allowed, or session expired. */\n\t| 'session-rejected'\n\t/** This session already has an active call (multi-tab). */\n\t| 'call-active'\n\t/** Rate/concurrency limit hit — retry later. */\n\t| 'rate-limited'\n\t/** No bot available right now — retry shortly. */\n\t| 'no-agent';\n\nexport class TalkifCallError extends Error {\n\treadonly code: TalkifCallErrorCode;\n\toverride readonly cause?: unknown;\n\n\tconstructor(code: TalkifCallErrorCode, message: string, cause?: unknown) {\n\t\tsuper(message);\n\t\tthis.name = 'TalkifCallError';\n\t\tthis.code = code;\n\t\tthis.cause = cause;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Configuration\n// ---------------------------------------------------------------------------\n\n/**\n * Supplies the Authorization header value for signaling requests.\n * Called per request so short-lived tokens (JWT refresh, widget session\n * tokens) stay current. Return e.g. `\"Bearer <token>\"`.\n */\nexport type AuthProvider = () => string | Promise<string>;\n\nexport interface TalkifClientConfig {\n\t/** API origin, e.g. `https://api.talkif.ai`. No trailing slash. */\n\tbaseUrl: string;\n\taccountId: string;\n\tauth: AuthProvider;\n\t/** Override fetch (tests, SSR polyfills). Defaults to global fetch. */\n\tfetch?: typeof fetch;\n}\n\n/**\n * Public (embeddable) configuration: the browser-safe publishable key from the\n * flow's public-access settings. The library exchanges it for a short-lived\n * session token itself — no bearer token, no account id. The page's Origin\n * must be on the key's allowlist.\n */\nexport interface TalkifPublicClientConfig {\n\t/** API origin, e.g. `https://api.talkif.ai`. No trailing slash. */\n\tbaseUrl: string;\n\t/** Publishable key (`pk_live_...`) — safe to ship in page source. */\n\tpublishableKey: string;\n\t/**\n\t * Cloudflare Turnstile sitekey (Talkif's — public, safe to ship). When set,\n\t * the library renders an invisible Turnstile challenge and produces a token\n\t * for each session exchange automatically. Leave unset to disable the gate\n\t * (dev/local, or before Turnstile is provisioned).\n\t */\n\tturnstileSiteKey?: string;\n\t/**\n\t * Manual Turnstile token supplier. Overrides `turnstileSiteKey` — supply\n\t * this only if you render Turnstile yourself. Called at each exchange.\n\t */\n\tturnstileToken?: () => string | Promise<string>;\n\t/** Override fetch (tests, SSR polyfills). Defaults to global fetch. */\n\tfetch?: typeof fetch;\n}\n\nexport type TalkifConfig = TalkifClientConfig | TalkifPublicClientConfig;\n\nexport function isPublicConfig(config: TalkifConfig): config is TalkifPublicClientConfig {\n\treturn 'publishableKey' in config;\n}\n\nexport interface StartCallOptions {\n\t/**\n\t * Start a call against a published flow. Required in authenticated mode.\n\t * Ignored in public mode — the flow is bound to the publishable key.\n\t */\n\tflowId?: string;\n\t/**\n\t * When set, uses the draft test-call endpoint with this definition instead\n\t * of the published flow.\n\t */\n\tdraftDefinition?: Record<string, unknown>;\n\tmetadata?: Record<string, unknown>;\n\t/** Constraints for getUserMedia. Defaults to `{ audio: true }`. */\n\taudio?: MediaTrackConstraints | boolean;\n\t/**\n\t * Attach remote audio to this element instead of an internally created\n\t * autoplay `Audio()`. Pass `null` to disable playback entirely (consumer\n\t * handles the `track` event itself).\n\t */\n\taudioElement?: HTMLAudioElement | null;\n}\n","/**\n * Cloudflare Turnstile integration — the \"prove you're a real browser\" gate\n * that stops scripted abuse of the public call surface.\n *\n * The customer does nothing: the widget self-injects Cloudflare's script,\n * renders an invisible Turnstile widget with Talkif's sitekey, and produces a\n * fresh token for each session exchange. The sitekey is public and safe to\n * ship; the matching secret lives only in the backend.\n */\n\nconst TURNSTILE_SCRIPT_URL =\n\t'https://challenges.cloudflare.com/turnstile/v0/api.js';\n\n/** Minimal shape of the global `turnstile` object we use. */\ninterface TurnstileApi {\n\trender(\n\t\tcontainer: HTMLElement,\n\t\toptions: {\n\t\t\tsitekey: string;\n\t\t\tcallback?: (token: string) => void;\n\t\t\t'error-callback'?: (code?: string) => void;\n\t\t\t'expired-callback'?: () => void;\n\t\t\tsize?: 'invisible' | 'normal' | 'compact' | 'flexible';\n\t\t\texecution?: 'render' | 'execute';\n\t\t\tretry?: 'auto' | 'never';\n\t\t}\n\t): string;\n\texecute(widgetId: string, options?: { sitekey?: string }): void;\n\treset(widgetId: string): void;\n\tremove(widgetId: string): void;\n}\n\ndeclare global {\n\tinterface Window {\n\t\tturnstile?: TurnstileApi;\n\t}\n}\n\nlet scriptPromise: Promise<void> | null = null;\n\n/** Load Cloudflare's Turnstile script once, shared across all instances. */\nfunction loadScript(): Promise<void> {\n\tif (typeof document === 'undefined') {\n\t\treturn Promise.reject(new Error('Turnstile requires a browser environment'));\n\t}\n\tif (window.turnstile) return Promise.resolve();\n\tscriptPromise ??= new Promise<void>((resolve, reject) => {\n\t\tconst existing = document.querySelector<HTMLScriptElement>(\n\t\t\t`script[src=\"${TURNSTILE_SCRIPT_URL}\"]`\n\t\t);\n\t\tif (existing) {\n\t\t\texisting.addEventListener('load', () => resolve());\n\t\t\texisting.addEventListener('error', () => reject(new Error('Turnstile script failed to load')));\n\t\t\tif (window.turnstile) resolve();\n\t\t\treturn;\n\t\t}\n\t\tconst script = document.createElement('script');\n\t\tscript.src = TURNSTILE_SCRIPT_URL;\n\t\tscript.async = true;\n\t\tscript.defer = true;\n\t\tscript.onload = () => resolve();\n\t\tscript.onerror = () => reject(new Error('Turnstile script failed to load'));\n\t\tdocument.head.appendChild(script);\n\t});\n\treturn scriptPromise;\n}\n\n/**\n * Manages one invisible Turnstile widget and hands out fresh tokens.\n *\n * Turnstile tokens are single-use and short-lived, so we `execute()` a fresh\n * challenge for each call to `getToken()` (i.e. each session exchange). The\n * widget is rendered lazily on first use and torn down with `dispose()`.\n */\nexport class TurnstileManager {\n\tprivate readonly siteKey: string;\n\tprivate container: HTMLDivElement | null = null;\n\tprivate widgetId: string | null = null;\n\tprivate pending: {\n\t\tresolve: (token: string) => void;\n\t\treject: (err: Error) => void;\n\t} | null = null;\n\n\tconstructor(siteKey: string) {\n\t\tthis.siteKey = siteKey;\n\t}\n\n\t/** Obtain a fresh Turnstile token. Rejects if the challenge fails. */\n\tasync getToken(): Promise<string> {\n\t\tawait loadScript();\n\t\tconst turnstile = window.turnstile;\n\t\tif (!turnstile) throw new Error('Turnstile unavailable after script load');\n\n\t\tif (!this.container) {\n\t\t\t// A hidden, off-screen host — the invisible widget never shows UI\n\t\t\t// but Cloudflare still requires an element to render into.\n\t\t\tthis.container = document.createElement('div');\n\t\t\tthis.container.style.position = 'fixed';\n\t\t\tthis.container.style.width = '0';\n\t\t\tthis.container.style.height = '0';\n\t\t\tthis.container.style.overflow = 'hidden';\n\t\t\tthis.container.style.pointerEvents = 'none';\n\t\t\tthis.container.setAttribute('aria-hidden', 'true');\n\t\t\tdocument.body.appendChild(this.container);\n\t\t}\n\n\t\tif (this.widgetId === null) {\n\t\t\tthis.widgetId = turnstile.render(this.container, {\n\t\t\t\tsitekey: this.siteKey,\n\t\t\t\tsize: 'invisible',\n\t\t\t\texecution: 'execute',\n\t\t\t\tretry: 'auto',\n\t\t\t\tcallback: (token: string) => {\n\t\t\t\t\tthis.pending?.resolve(token);\n\t\t\t\t\tthis.pending = null;\n\t\t\t\t},\n\t\t\t\t'error-callback': (code?: string) => {\n\t\t\t\t\tthis.pending?.reject(new Error(`Turnstile challenge failed${code ? `: ${code}` : ''}`));\n\t\t\t\t\tthis.pending = null;\n\t\t\t\t},\n\t\t\t\t'expired-callback': () => {\n\t\t\t\t\t// Token expired before use — the next getToken() re-executes.\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\treturn new Promise<string>((resolve, reject) => {\n\t\t\t// Only one outstanding challenge at a time; supersede any prior.\n\t\t\tif (this.pending) {\n\t\t\t\tthis.pending.reject(new Error('Turnstile challenge superseded'));\n\t\t\t}\n\t\t\tthis.pending = { resolve, reject };\n\t\t\tconst id = this.widgetId;\n\t\t\tif (id === null) {\n\t\t\t\treject(new Error('Turnstile widget not rendered'));\n\t\t\t\tthis.pending = null;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tturnstile.reset(id);\n\t\t\t\tturnstile.execute(id, { sitekey: this.siteKey });\n\t\t\t} catch (err) {\n\t\t\t\tthis.pending = null;\n\t\t\t\treject(err instanceof Error ? err : new Error('Turnstile execute failed'));\n\t\t\t}\n\t\t});\n\t}\n\n\tdispose(): void {\n\t\tif (this.widgetId !== null && window.turnstile) {\n\t\t\ttry {\n\t\t\t\twindow.turnstile.remove(this.widgetId);\n\t\t\t} catch {\n\t\t\t\t// best-effort\n\t\t\t}\n\t\t}\n\t\tthis.widgetId = null;\n\t\tthis.pending = null;\n\t\tif (this.container) {\n\t\t\tthis.container.remove();\n\t\t\tthis.container = null;\n\t\t}\n\t}\n}\n","import type {\n\tCreateCallRequest,\n\tCreateCallResponse,\n\tCreateTestCallRequest,\n\tIceServersResponse,\n\tOfferRequest,\n\tOfferResponse,\n\tPublicSessionResponse,\n\tTalkifConfig,\n\tTalkifPublicClientConfig,\n\tWebRTCCall,\n} from './types.js';\nimport { isPublicConfig, TalkifCallError } from './types.js';\nimport { TurnstileManager } from './turnstile.js';\n\nexport class SignalingError extends Error {\n\treadonly status: number;\n\treadonly body: unknown;\n\n\tconstructor(status: number, message: string, body: unknown) {\n\t\tsuper(message);\n\t\tthis.name = 'SignalingError';\n\t\tthis.status = status;\n\t\tthis.body = body;\n\t}\n}\n\n/** Re-exchange the publishable key this many ms before the token expires. */\nconst SESSION_REFRESH_MARGIN_MS = 60_000;\n\n/**\n * Holds the short-lived session token for the public surface: exchanges the\n * publishable key on first use, re-exchanges near expiry, and can be\n * invalidated on a 401 so the next request retries with a fresh token.\n */\nexport class PublicSession {\n\tprivate readonly config: TalkifPublicClientConfig;\n\tprivate token: string | null = null;\n\tprivate expiresAt = 0;\n\tprivate flowIdValue: string | null = null;\n\tprivate inflight: Promise<string> | null = null;\n\tprivate turnstile: TurnstileManager | null = null;\n\n\tconstructor(config: TalkifPublicClientConfig) {\n\t\tthis.config = config;\n\t\t// Built-in Turnstile: render an invisible challenge from the sitekey,\n\t\t// unless the caller supplies their own token provider.\n\t\tif (config.turnstileSiteKey && !config.turnstileToken) {\n\t\t\tthis.turnstile = new TurnstileManager(config.turnstileSiteKey);\n\t\t}\n\t}\n\n\t/** A fresh Turnstile token, from the manual supplier or the built-in\n\t * manager; undefined when no gate is configured (dev/local). */\n\tprivate async turnstileToken(): Promise<string | undefined> {\n\t\tif (this.config.turnstileToken) {\n\t\t\treturn this.config.turnstileToken();\n\t\t}\n\t\tif (this.turnstile) {\n\t\t\treturn this.turnstile.getToken();\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/** Tear down the Turnstile widget (call when the client is disposed). */\n\tdispose(): void {\n\t\tthis.turnstile?.dispose();\n\t\tthis.turnstile = null;\n\t}\n\n\t/** Flow bound to the publishable key (known after the first exchange). */\n\tget flowId(): string | null {\n\t\treturn this.flowIdValue;\n\t}\n\n\tasync getToken(): Promise<string> {\n\t\tif (this.token && Date.now() < this.expiresAt - SESSION_REFRESH_MARGIN_MS) {\n\t\t\treturn this.token;\n\t\t}\n\t\t// Single-flight the exchange: concurrent requests share one round-trip.\n\t\tthis.inflight ??= this.exchange().finally(() => {\n\t\t\tthis.inflight = null;\n\t\t});\n\t\treturn this.inflight;\n\t}\n\n\tinvalidate(): void {\n\t\tthis.token = null;\n\t\tthis.expiresAt = 0;\n\t}\n\n\tprivate async exchange(): Promise<string> {\n\t\tconst doFetch = this.config.fetch ?? fetch;\n\t\tconst turnstileToken = await this.turnstileToken();\n\t\tconst response = await doFetch(\n\t\t\t`${this.config.baseUrl.replace(/\\/$/, '')}/api/v1/public/calls/session`,\n\t\t\t{\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: { 'content-type': 'application/json' },\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tpublishableKey: this.config.publishableKey,\n\t\t\t\t\t...(turnstileToken !== undefined ? { turnstileToken } : {}),\n\t\t\t\t}),\n\t\t\t}\n\t\t);\n\t\tconst parsed: unknown = await response.json().catch(() => null);\n\t\tif (!response.ok) {\n\t\t\tthrow new SignalingError(\n\t\t\t\tresponse.status,\n\t\t\t\t'Session exchange failed — check the publishable key and that this origin is on its allowlist',\n\t\t\t\tparsed\n\t\t\t);\n\t\t}\n\t\tconst session = parsed as PublicSessionResponse;\n\t\tthis.token = session.token;\n\t\tthis.expiresAt = Date.now() + session.expiresIn * 1_000;\n\t\tthis.flowIdValue = session.flowId;\n\t\treturn session.token;\n\t}\n}\n\n/**\n * Thin HTTP client for the Talkif WebRTC signaling routes. Two modes:\n * - authenticated (dashboard/API): `/api/v1/accounts/{accountId}/calls/webrtc`\n * - public (embed): `/api/v1/public/calls`, session token minted from the\n * publishable key and refreshed transparently.\n */\nexport class SignalingClient {\n\tprivate readonly config: TalkifConfig;\n\treadonly session: PublicSession | null;\n\n\tconstructor(config: TalkifConfig) {\n\t\tthis.config = config;\n\t\tthis.session = isPublicConfig(config) ? new PublicSession(config) : null;\n\t}\n\n\tget isPublic(): boolean {\n\t\treturn this.session !== null;\n\t}\n\n\tprivate get base(): string {\n\t\tconst baseUrl = this.config.baseUrl.replace(/\\/$/, '');\n\t\tif (isPublicConfig(this.config)) {\n\t\t\treturn `${baseUrl}/api/v1/public/calls`;\n\t\t}\n\t\treturn `${baseUrl}/api/v1/accounts/${this.config.accountId}/calls/webrtc`;\n\t}\n\n\tprivate async authorization(): Promise<string> {\n\t\tif (this.session) {\n\t\t\treturn `Bearer ${await this.session.getToken()}`;\n\t\t}\n\t\treturn (this.config as { auth: () => string | Promise<string> }).auth();\n\t}\n\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST',\n\t\tpath: string,\n\t\tbody?: unknown,\n\t\tretriedAuth = false\n\t): Promise<T> {\n\t\tconst doFetch = this.config.fetch ?? fetch;\n\t\tconst authorization = await this.authorization();\n\t\tconst response = await doFetch(`${this.base}${path}`, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\tauthorization,\n\t\t\t\t...(body !== undefined ? { 'content-type': 'application/json' } : {}),\n\t\t\t},\n\t\t\t...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n\t\t});\n\n\t\t// Public mode: an expired/rotated session token gets one transparent\n\t\t// re-exchange + retry before surfacing the failure.\n\t\tif (response.status === 401 && this.session && !retriedAuth) {\n\t\t\tthis.session.invalidate();\n\t\t\treturn this.request(method, path, body, true);\n\t\t}\n\n\t\tlet parsed: unknown = null;\n\t\tconst text = await response.text();\n\t\tif (text) {\n\t\t\ttry {\n\t\t\t\tparsed = JSON.parse(text);\n\t\t\t} catch {\n\t\t\t\tparsed = text;\n\t\t\t}\n\t\t}\n\n\t\tif (!response.ok) {\n\t\t\tconst message =\n\t\t\t\ttypeof parsed === 'object' && parsed !== null && 'detail' in parsed\n\t\t\t\t\t? String((parsed as { detail: unknown }).detail)\n\t\t\t\t\t: typeof parsed === 'object' && parsed !== null && 'message' in parsed\n\t\t\t\t\t\t? String((parsed as { message: unknown }).message)\n\t\t\t\t\t\t: `Signaling request failed: ${method} ${path} → ${response.status}`;\n\t\t\tthrow new SignalingError(response.status, message, parsed);\n\t\t}\n\t\treturn parsed as T;\n\t}\n\n\tcreateCall(body: CreateCallRequest): Promise<CreateCallResponse> {\n\t\tif (this.isPublic) {\n\t\t\t// Flow is bound to the publishable key server-side; body is empty.\n\t\t\treturn this.request('POST', '/calls', {});\n\t\t}\n\t\treturn this.request('POST', '', body);\n\t}\n\n\tcreateTestCall(body: CreateTestCallRequest): Promise<CreateCallResponse> {\n\t\tif (this.isPublic) {\n\t\t\tthrow new TalkifCallError(\n\t\t\t\t'signaling-failed',\n\t\t\t\t'Draft test calls are not available on the public surface'\n\t\t\t);\n\t\t}\n\t\treturn this.request('POST', '/test', body);\n\t}\n\n\tgetIceServers(): Promise<IceServersResponse> {\n\t\treturn this.request('GET', '/ice-servers');\n\t}\n\n\tsendOffer(callId: string, body: OfferRequest): Promise<OfferResponse> {\n\t\tconst path = this.isPublic ? `/calls/${callId}/offer` : `/${callId}/offer`;\n\t\treturn this.request('POST', path, body);\n\t}\n\n\tgetCall(callId: string): Promise<WebRTCCall> {\n\t\tconst path = this.isPublic ? `/calls/${callId}` : `/${callId}`;\n\t\treturn this.request('GET', path);\n\t}\n}\n\n/** Map a signaling failure to the library's typed error. */\nexport function asSignalingCallError(error: unknown): TalkifCallError {\n\tif (error instanceof TalkifCallError) return error;\n\tif (error instanceof SignalingError) {\n\t\tconst code =\n\t\t\terror.status === 401\n\t\t\t\t? 'session-rejected'\n\t\t\t\t: error.status === 409\n\t\t\t\t\t? 'call-active'\n\t\t\t\t\t: error.status === 429\n\t\t\t\t\t\t? 'rate-limited'\n\t\t\t\t\t\t: error.status === 503\n\t\t\t\t\t\t\t? 'no-agent'\n\t\t\t\t\t\t\t: 'signaling-failed';\n\t\treturn new TalkifCallError(code, error.message, error);\n\t}\n\tconst message = error instanceof Error ? error.message : 'Signaling request failed';\n\treturn new TalkifCallError('signaling-failed', message, error);\n}\n","import { DataChannel } from './data-channel.js';\nimport { Emitter } from './emitter.js';\nimport { CallEventsClient } from './events.js';\nimport { asSignalingCallError, SignalingClient } from './signaling.js';\nimport type {\n\tAppMessage,\n\tCallEndReason,\n\tCallEventEnvelope,\n\tCallState,\n\tIceServer,\n\tSignallingPayload,\n\tStartCallOptions,\n\tTalkifCallEventMap,\n\tTalkifConfig,\n} from './types.js';\nimport { TalkifCallError, TERMINAL_CALL_STATUSES } from './types.js';\n\n// If no external health confirmation (SSE/app message) arrives within this\n// window after media connects, verify call health against the backend over\n// REST before tearing down. Silence alone is NOT proof of pipeline death.\nconst PIPELINE_HEALTH_TIMEOUT_MS = 15_000;\n\n// Safety net for ICE gathering: send whatever we have if no relay candidate\n// shows up. With iceTransportPolicy 'relay' the first relay(udp) candidate is\n// all the bot needs; TURN tcp/443 allocations routinely never complete, so\n// waiting for iceGatheringState === 'complete' would stall every call.\nconst ICE_FIRST_RELAY_TIMEOUT_MS = 1_500;\n\n/**\n * One WebRTC voice session with a Talkif agent.\n *\n * Lifecycle: `idle → requesting → connecting → connected → ended | error`.\n * A TalkifCall instance is single-use: create a new one per call.\n */\nexport class TalkifCall {\n\tprivate readonly signaling: SignalingClient;\n\tprivate readonly config: TalkifConfig;\n\tprivate readonly emitter = new Emitter<TalkifCallEventMap>();\n\tprivate events: CallEventsClient | null = null;\n\n\tprivate pc: RTCPeerConnection | null = null;\n\tprivate localStream: MediaStream | null = null;\n\tprivate remoteAudio: HTMLAudioElement | null = null;\n\tprivate ownsAudioElement = false;\n\tprivate dataChannel: DataChannel | null = null;\n\tprivate iceServers: IceServer[] = [];\n\n\tprivate durationTimer: ReturnType<typeof setInterval> | null = null;\n\tprivate healthTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate pipelineConfirmed = false;\n\tprivate renegotiating = false;\n\n\tprivate _state: CallState = 'idle';\n\tprivate _callId: string | null = null;\n\tprivate _botId: string | null = null;\n\tprivate _muted = false;\n\tprivate _durationSecs = 0;\n\n\tconstructor(config: TalkifConfig) {\n\t\tthis.config = config;\n\t\tthis.signaling = new SignalingClient(config);\n\t}\n\n\t// -- public surface -------------------------------------------------------\n\n\tget state(): CallState {\n\t\treturn this._state;\n\t}\n\n\tget callId(): string | null {\n\t\treturn this._callId;\n\t}\n\n\tget botId(): string | null {\n\t\treturn this._botId;\n\t}\n\n\tget muted(): boolean {\n\t\treturn this._muted;\n\t}\n\n\tget durationSecs(): number {\n\t\treturn this._durationSecs;\n\t}\n\n\ton<K extends keyof TalkifCallEventMap>(\n\t\tevent: K,\n\t\tlistener: (payload: TalkifCallEventMap[K]) => void\n\t): () => void {\n\t\treturn this.emitter.on(event, listener);\n\t}\n\n\toff<K extends keyof TalkifCallEventMap>(\n\t\tevent: K,\n\t\tlistener: (payload: TalkifCallEventMap[K]) => void\n\t): void {\n\t\tthis.emitter.off(event, listener);\n\t}\n\n\t/**\n\t * External health signal (e.g. the dashboard's SSE layer saw a\n\t * RUNNING/CONNECTED status). Cancels the REST health watchdog.\n\t */\n\tconfirmPipelineHealthy(): void {\n\t\tthis.pipelineConfirmed = true;\n\t\tif (this.healthTimer) {\n\t\t\tclearTimeout(this.healthTimer);\n\t\t\tthis.healthTimer = null;\n\t\t}\n\t}\n\n\t/**\n\t * External terminal signal (e.g. SSE reported COMPLETED). Tears down and\n\t * transitions to 'ended'.\n\t */\n\tendedExternally(): void {\n\t\tif (this._state !== 'connecting' && this._state !== 'connected') return;\n\t\tthis.teardown();\n\t\tthis.setState('ended');\n\t\tthis.emitter.emit('ended', { reason: 'external' });\n\t}\n\n\t/** Send an arbitrary JSON app message to the bot over the data channel. */\n\tsendAppMessage(message: AppMessage): boolean {\n\t\treturn this.dataChannel?.sendAppMessage(message) ?? false;\n\t}\n\n\tsetMuted(muted: boolean): void {\n\t\tif (!this.localStream) return;\n\t\tfor (const track of this.localStream.getAudioTracks()) {\n\t\t\ttrack.enabled = !muted;\n\t\t}\n\t\tthis._muted = muted;\n\t\tthis.emitter.emit('mutechange', { muted });\n\t}\n\n\t/** User-initiated hangup. */\n\thangup(): void {\n\t\tif (this._state === 'idle' || this._state === 'ended' || this._state === 'error') return;\n\t\tthis.teardown();\n\t\tthis.setState('ended');\n\t\tthis.emitter.emit('ended', { reason: 'local-hangup' });\n\t}\n\n\t/** Full teardown regardless of state — safe to call multiple times. */\n\tdispose(): void {\n\t\tthis.teardown();\n\t\tthis.signaling.session?.dispose();\n\t\tthis.emitter.removeAll();\n\t}\n\n\t// -- lifecycle ------------------------------------------------------------\n\n\tasync start(options: StartCallOptions): Promise<void> {\n\t\tif (this._state !== 'idle') {\n\t\t\tthrow new TalkifCallError('signaling-failed', `Cannot start a call from state '${this._state}' — create a new TalkifCall`);\n\t\t}\n\t\tthis.setState('requesting');\n\n\t\tif (!this.signaling.isPublic && !options.flowId) {\n\t\t\tthrow new TalkifCallError('signaling-failed', 'flowId is required in authenticated mode');\n\t\t}\n\n\t\ttry {\n\t\t\t// Run bot warmup (create call → bot assignment, 2-5s) in parallel with\n\t\t\t// local setup (ICE fetch, mic, offer, candidate gathering).\n\t\t\tconst flowId = options.flowId ?? '';\n\t\t\tconst [callResponse, local] = await Promise.all([\n\t\t\t\toptions.draftDefinition !== undefined\n\t\t\t\t\t? this.signaling.createTestCall({\n\t\t\t\t\t\t\tflowId,\n\t\t\t\t\t\t\tdefinition: options.draftDefinition,\n\t\t\t\t\t\t\t...(options.metadata !== undefined ? { metadata: options.metadata } : {}),\n\t\t\t\t\t\t})\n\t\t\t\t\t: this.signaling.createCall({\n\t\t\t\t\t\t\tflowId,\n\t\t\t\t\t\t\t...(options.metadata !== undefined ? { metadata: options.metadata } : {}),\n\t\t\t\t\t\t}),\n\t\t\t\tthis.prepareLocal(options),\n\t\t\t]);\n\n\t\t\tthis._callId = callResponse.callId;\n\t\t\tthis.pc = local.pc;\n\t\t\tthis.localStream = local.stream;\n\t\t\tthis.setState('connecting');\n\n\t\t\t// Subscribe to events before the media handshake so the WS is\n\t\t\t// listening before the bot speaks (the session→call mapping already\n\t\t\t// exists once create-call returns).\n\t\t\tthis.connectEvents();\n\n\t\t\tthis.pc.ontrack = (event) => {\n\t\t\t\tconst stream = event.streams[0];\n\t\t\t\tif (!stream) return;\n\t\t\t\tthis.attachRemoteAudio(stream, options);\n\t\t\t\tthis.emitter.emit('track', { stream });\n\t\t\t};\n\n\t\t\tthis.pc.onconnectionstatechange = () => {\n\t\t\t\tconst state = this.pc?.connectionState;\n\t\t\t\tif (state === 'failed' || state === 'disconnected') {\n\t\t\t\t\tthis.fail(new TalkifCallError('connection-failed', 'WebRTC connection failed'));\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tthis.dataChannel = new DataChannel(this.pc, {\n\t\t\t\tonSignalling: (message) => this.handleSignalling(message),\n\t\t\t\tonAppMessage: (message) => this.emitter.emit('appmessage', { message }),\n\t\t\t});\n\n\t\t\tconst answer = await this.signaling.sendOffer(callResponse.callId, {\n\t\t\t\tsdp: this.mustLocalSdp(),\n\t\t\t\ticeServers: this.iceServers,\n\t\t\t});\n\t\t\tawait this.pc.setRemoteDescription({ type: 'answer', sdp: answer.sdp });\n\t\t\tthis._botId = answer.botId ?? null;\n\n\t\t\tthis.setState('connected');\n\t\t\tthis.emitter.emit('connected', { callId: callResponse.callId, botId: this._botId });\n\t\t\tthis.startDurationTimer();\n\t\t\tthis.armHealthWatchdog(callResponse.callId);\n\t\t} catch (error) {\n\t\t\tthis.fail(\n\t\t\t\terror instanceof TalkifCallError ? error : asSignalingCallError(error)\n\t\t\t);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tprivate async prepareLocal(options: StartCallOptions): Promise<{ pc: RTCPeerConnection; stream: MediaStream }> {\n\t\tconst { iceServers } = await this.signaling.getIceServers();\n\t\tthis.iceServers = iceServers;\n\n\t\tif (!navigator.mediaDevices?.getUserMedia) {\n\t\t\tthrow new TalkifCallError('media-unavailable', 'Microphone access unavailable. This requires HTTPS or localhost.');\n\t\t}\n\t\tconst stream = await navigator.mediaDevices\n\t\t\t.getUserMedia({ audio: options.audio ?? true })\n\t\t\t.catch((cause: unknown) => {\n\t\t\t\tthrow new TalkifCallError('media-unavailable', 'Microphone permission denied or unavailable', cause);\n\t\t\t});\n\n\t\t// Relay-only transport: skips host/srflx gathering (no dead-interface\n\t\t// timeouts), works through firewalls that block UDP, avoids TURN\n\t\t// \"Forbidden IP\" errors for private IPs, and gathers in ~100-200ms.\n\t\tconst pc = new RTCPeerConnection({\n\t\t\ticeServers: iceServers.map((s) => ({\n\t\t\t\turls: s.urls,\n\t\t\t\t...(s.username !== undefined ? { username: s.username } : {}),\n\t\t\t\t...(s.credential !== undefined ? { credential: s.credential } : {}),\n\t\t\t})),\n\t\t\ticeTransportPolicy: 'relay',\n\t\t});\n\t\tfor (const track of stream.getTracks()) {\n\t\t\tpc.addTrack(track, stream);\n\t\t}\n\n\t\tconst offer = await pc.createOffer();\n\t\tawait pc.setLocalDescription(offer);\n\t\tif (!offer.sdp) {\n\t\t\tthrow new TalkifCallError('signaling-failed', 'Failed to create SDP offer');\n\t\t}\n\n\t\tawait this.waitForFirstRelayCandidate(pc);\n\t\treturn { pc, stream };\n\t}\n\n\tprivate waitForFirstRelayCandidate(pc: RTCPeerConnection): Promise<void> {\n\t\treturn new Promise<void>((resolve) => {\n\t\t\tif (pc.iceGatheringState === 'complete') {\n\t\t\t\tresolve();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tlet settled = false;\n\t\t\tconst finish = () => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tpc.removeEventListener('icecandidate', onCandidate);\n\t\t\t\tpc.removeEventListener('icegatheringstatechange', onGatheringComplete);\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tconst timeout = setTimeout(finish, ICE_FIRST_RELAY_TIMEOUT_MS);\n\t\t\tconst onCandidate = (e: RTCPeerConnectionIceEvent) => {\n\t\t\t\tif (e.candidate && e.candidate.type === 'relay') finish();\n\t\t\t};\n\t\t\tconst onGatheringComplete = () => {\n\t\t\t\tif (pc.iceGatheringState === 'complete') finish();\n\t\t\t};\n\t\t\tpc.addEventListener('icecandidate', onCandidate);\n\t\t\tpc.addEventListener('icegatheringstatechange', onGatheringComplete);\n\t\t});\n\t}\n\n\tprivate attachRemoteAudio(stream: MediaStream, options: StartCallOptions): void {\n\t\tif (options.audioElement === null) return; // consumer opted out\n\t\tif (!this.remoteAudio) {\n\t\t\tif (options.audioElement) {\n\t\t\t\tthis.remoteAudio = options.audioElement;\n\t\t\t\tthis.ownsAudioElement = false;\n\t\t\t} else {\n\t\t\t\tthis.remoteAudio = new Audio();\n\t\t\t\tthis.remoteAudio.autoplay = true;\n\t\t\t\tthis.ownsAudioElement = true;\n\t\t\t}\n\t\t}\n\t\tthis.remoteAudio.srcObject = stream;\n\t}\n\n\t// -- data-channel protocol ------------------------------------------------\n\n\tprivate handleSignalling(message: SignallingPayload): void {\n\t\tswitch (message.type) {\n\t\t\tcase 'peerLeft':\n\t\t\t\tif (this._state === 'connected' || this._state === 'connecting') {\n\t\t\t\t\tthis.teardown();\n\t\t\t\t\tthis.setState('ended');\n\t\t\t\t\tthis.emitter.emit('ended', { reason: 'peer-left' });\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase 'renegotiate':\n\t\t\t\tvoid this.renegotiate();\n\t\t\t\tbreak;\n\t\t\tcase 'trackStatus':\n\t\t\t\t// Bot→client trackStatus is not part of the current protocol; ignore.\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t/**\n\t * Bot asked for a new offer/answer cycle. Re-offer through the same relay\n\t * endpoint. If the backend rejects re-offers (older deployments), the call\n\t * keeps running on the existing session — surfaced via `renegotiated`.\n\t */\n\tprivate async renegotiate(): Promise<void> {\n\t\tif (!this.pc || !this._callId || this.renegotiating) return;\n\t\tthis.renegotiating = true;\n\t\ttry {\n\t\t\tconst offer = await this.pc.createOffer({ iceRestart: true });\n\t\t\tawait this.pc.setLocalDescription(offer);\n\t\t\tawait this.waitForFirstRelayCandidate(this.pc);\n\t\t\tconst answer = await this.signaling.sendOffer(this._callId, {\n\t\t\t\tsdp: this.mustLocalSdp(),\n\t\t\t\ticeServers: this.iceServers,\n\t\t\t});\n\t\t\tawait this.pc.setRemoteDescription({ type: 'answer', sdp: answer.sdp });\n\t\t\tthis.emitter.emit('renegotiated', { ok: true });\n\t\t} catch {\n\t\t\tthis.emitter.emit('renegotiated', { ok: false });\n\t\t} finally {\n\t\t\tthis.renegotiating = false;\n\t\t}\n\t}\n\n\t// -- realtime events (public mode) ----------------------------------------\n\n\t/**\n\t * Public mode only: open the realtime events WebSocket for this call.\n\t * Events double as the pipeline-health signal, and a terminal `status`\n\t * event ends the call from the server side (e.g. max-duration reached).\n\t */\n\tprivate connectEvents(): void {\n\t\tconst session = this.signaling.session;\n\t\tif (!session) return; // authenticated mode: dashboard SSE covers events\n\n\t\tthis.events = new CallEventsClient({\n\t\t\tbaseUrl: this.config.baseUrl,\n\t\t\tgetToken: () => session.getToken(),\n\t\t\tonEvent: (event) => this.handleCallEvent(event),\n\t\t});\n\t\tthis.events.connect();\n\t}\n\n\tprivate handleCallEvent(event: CallEventEnvelope): void {\n\t\t// Events flowing at all = the bot pipeline is alive.\n\t\tthis.confirmPipelineHealthy();\n\t\tthis.emitter.emit('callevent', { event });\n\n\t\tswitch (event.type) {\n\t\t\tcase 'transcript': {\n\t\t\t\tconst role = typeof event.data.role === 'string' ? event.data.role : 'unknown';\n\t\t\t\tconst content = typeof event.data.content === 'string' ? event.data.content : '';\n\t\t\t\tthis.emitter.emit('transcript', { role, content, data: event.data });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'interim':\n\t\t\t\tthis.emitter.emit('interim', { data: event.data });\n\t\t\t\tbreak;\n\t\t\tcase 'tts': {\n\t\t\t\tconst text = typeof event.data.text === 'string' ? event.data.text : '';\n\t\t\t\tif (!text) break;\n\t\t\t\tconst timestamp = typeof event.data.timestamp === 'number' ? event.data.timestamp : 0;\n\t\t\t\tthis.emitter.emit('ttschunk', { text, timestamp, data: event.data });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'tts_word': {\n\t\t\t\tconst word = typeof event.data.word === 'string' ? event.data.word : '';\n\t\t\t\tif (!word) break;\n\t\t\t\t// Backend serializes camelCase (`ptsMs`); accept snake_case too\n\t\t\t\t// for resilience against older/other emitters.\n\t\t\t\tconst ptsRaw = event.data.ptsMs ?? event.data.pts_ms;\n\t\t\t\tconst ptsMs = typeof ptsRaw === 'number' ? ptsRaw : null;\n\t\t\t\tconst timestamp = typeof event.data.timestamp === 'number' ? event.data.timestamp : 0;\n\t\t\t\tthis.emitter.emit('ttsword', { word, ptsMs, timestamp, data: event.data });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'status': {\n\t\t\t\tconst status = typeof event.data.status === 'string' ? event.data.status : '';\n\t\t\t\tif (TERMINAL_CALL_STATUSES.has(status.toUpperCase() as never)) {\n\t\t\t\t\tthis.endedExternally();\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// -- health watchdog ------------------------------------------------------\n\n\tprivate armHealthWatchdog(callId: string): void {\n\t\tthis.pipelineConfirmed = false;\n\t\tthis.healthTimer = setTimeout(() => {\n\t\t\tthis.healthTimer = null;\n\t\t\tif (this.pipelineConfirmed) return;\n\t\t\tvoid this.verifyPipelineHealth(callId);\n\t\t}, PIPELINE_HEALTH_TIMEOUT_MS);\n\t}\n\n\tprivate async verifyPipelineHealth(callId: string): Promise<void> {\n\t\ttry {\n\t\t\tconst call = await this.signaling.getCall(callId);\n\t\t\tif (this.pipelineConfirmed || !this.pc) return;\n\t\t\tif (TERMINAL_CALL_STATUSES.has(call.status)) {\n\t\t\t\tthis.fail(new TalkifCallError('pipeline-dead', `Backend reports terminal status '${call.status}'`));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// Backend says the call is alive — the silence was the event layer's\n\t\t\t// fault, not the pipeline's.\n\t\t\tthis.pipelineConfirmed = true;\n\t\t} catch {\n\t\t\tif (this.pipelineConfirmed || !this.pc) return;\n\t\t\t// REST unreachable — fall back to the media-plane signal. Flowing\n\t\t\t// audio means alive; later media death is caught by\n\t\t\t// onconnectionstatechange. Kill only when both signals are gone.\n\t\t\tif (this.pc.connectionState === 'connected') {\n\t\t\t\tthis.pipelineConfirmed = true;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.fail(new TalkifCallError('pipeline-dead', 'No health signal, REST unreachable, media not connected'));\n\t\t}\n\t}\n\n\t// -- internals ------------------------------------------------------------\n\n\tprivate mustLocalSdp(): string {\n\t\tconst sdp = this.pc?.localDescription?.sdp;\n\t\tif (!sdp) {\n\t\t\tthrow new TalkifCallError('signaling-failed', 'Local SDP missing');\n\t\t}\n\t\treturn sdp;\n\t}\n\n\tprivate startDurationTimer(): void {\n\t\tthis._durationSecs = 0;\n\t\tthis.durationTimer = setInterval(() => {\n\t\t\tthis._durationSecs += 1;\n\t\t\tthis.emitter.emit('tick', { durationSecs: this._durationSecs });\n\t\t}, 1_000);\n\t}\n\n\tprivate fail(error: TalkifCallError): void {\n\t\tif (this._state === 'ended' || this._state === 'error') return;\n\t\tthis.teardown();\n\t\tthis.setState('error');\n\t\tthis.emitter.emit('error', { error });\n\t}\n\n\tprivate setState(state: CallState): void {\n\t\tconst previous = this._state;\n\t\tif (previous === state) return;\n\t\tthis._state = state;\n\t\tthis.emitter.emit('statechange', { state, previous });\n\t}\n\n\tprivate teardown(): void {\n\t\tthis.events?.close();\n\t\tthis.events = null;\n\n\t\tif (this.healthTimer) {\n\t\t\tclearTimeout(this.healthTimer);\n\t\t\tthis.healthTimer = null;\n\t\t}\n\t\tthis.pipelineConfirmed = false;\n\n\t\tif (this.durationTimer) {\n\t\t\tclearInterval(this.durationTimer);\n\t\t\tthis.durationTimer = null;\n\t\t}\n\n\t\tthis.dataChannel?.close();\n\t\tthis.dataChannel = null;\n\n\t\tif (this.localStream) {\n\t\t\tfor (const track of this.localStream.getTracks()) track.stop();\n\t\t\tthis.localStream = null;\n\t\t}\n\n\t\tif (this.remoteAudio) {\n\t\t\tthis.remoteAudio.pause();\n\t\t\tthis.remoteAudio.srcObject = null;\n\t\t\tif (this.ownsAudioElement) this.remoteAudio = null;\n\t\t}\n\n\t\tif (this.pc) {\n\t\t\tthis.pc.ontrack = null;\n\t\t\tthis.pc.onconnectionstatechange = null;\n\t\t\tthis.pc.close();\n\t\t\tthis.pc = null;\n\t\t}\n\t}\n}\n\nexport type { CallEndReason };\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talkif/webrtc",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Talkif WebRTC client — voice + text sessions with Talkif AI agents from the browser",
5
5
  "license": "MIT",
6
6
  "repository": {