@coventia/softphone 0.2.0 → 0.3.0

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.
Files changed (47) hide show
  1. package/dist/{call-DUVnDxsz.d.cts → call-CaPDWVk3.d.cts} +50 -1
  2. package/dist/{call-DUVnDxsz.d.ts → call-CaPDWVk3.d.ts} +50 -1
  3. package/dist/{chunk-YWI6EQYS.js → chunk-EYEDPD7Y.js} +108 -15
  4. package/dist/chunk-EYEDPD7Y.js.map +1 -0
  5. package/dist/{chunk-LMJXX4YM.js → chunk-UHNLEBBO.js} +4 -2
  6. package/dist/chunk-UHNLEBBO.js.map +1 -0
  7. package/dist/element/index.d.ts +76 -11
  8. package/dist/element/index.js +1411 -478
  9. package/dist/element/index.js.map +1 -1
  10. package/dist/{engine-EPCKf-Bl.d.ts → engine-BgBsIcEa.d.ts} +1 -1
  11. package/dist/{engine-CDjkrXHY.d.cts → engine-CU2rEo3g.d.cts} +1 -1
  12. package/dist/index.cjs +318 -32
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +5 -5
  15. package/dist/index.d.ts +5 -5
  16. package/dist/index.js +157 -19
  17. package/dist/index.js.map +1 -1
  18. package/dist/react/index.cjs +2 -0
  19. package/dist/react/index.cjs.map +1 -1
  20. package/dist/react/index.d.cts +2 -2
  21. package/dist/react/index.d.ts +2 -2
  22. package/dist/react/index.js +1 -1
  23. package/dist/{softphone-BtdgZWkm.d.ts → softphone-DEt0TtQw.d.ts} +26 -10
  24. package/dist/{softphone-DHG9MTNg.d.cts → softphone-dZMkW8V1.d.cts} +26 -10
  25. package/dist/softphone.css +192 -46
  26. package/dist/{sources-5L2R4HTV.js → sources-M3GYMS6V.js} +58 -3
  27. package/dist/sources-M3GYMS6V.js.map +1 -0
  28. package/dist/testing/index.d.cts +2 -2
  29. package/dist/testing/index.d.ts +2 -2
  30. package/dist/ui/index.cjs +1308 -366
  31. package/dist/ui/index.cjs.map +1 -1
  32. package/dist/ui/index.d.cts +40 -7
  33. package/dist/ui/index.d.ts +40 -7
  34. package/dist/ui/index.js +1308 -369
  35. package/dist/ui/index.js.map +1 -1
  36. package/dist/zadarma/index.cjs +106 -13
  37. package/dist/zadarma/index.cjs.map +1 -1
  38. package/dist/zadarma/index.d.cts +2 -2
  39. package/dist/zadarma/index.d.ts +2 -2
  40. package/dist/zadarma/index.js +1 -1
  41. package/dist/zadarma-engine-L5DEFXUS.js +5 -0
  42. package/dist/{zadarma-engine-4VOPIBXG.js.map → zadarma-engine-L5DEFXUS.js.map} +1 -1
  43. package/package.json +1 -1
  44. package/dist/chunk-LMJXX4YM.js.map +0 -1
  45. package/dist/chunk-YWI6EQYS.js.map +0 -1
  46. package/dist/sources-5L2R4HTV.js.map +0 -1
  47. package/dist/zadarma-engine-4VOPIBXG.js +0 -5
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/emitter.ts","../src/core/call.ts","../src/core/call-impl.ts","../src/core/backend.ts","../src/core/tabs.ts","../src/core/transfer-watch.ts","../src/core/presence-socket.ts","../src/core/softphone-impl.ts","../src/core/default-provider.ts","../src/core/create.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { TypedEmitter } from './events.js';\r\n\r\n/**\r\n * A tiny typed event emitter. Framework-free — no Node `events`, no browser globals (SPEC §10.2).\r\n *\r\n * `on()` / `once()` return an unsubscribe function (SPEC §5.3 / events.ts): cleanup is impossible to\r\n * get wrong in a React effect, and there is no opaque handle to retain (Vonage's mistake).\r\n *\r\n * A listener that throws must not stop the other listeners or corrupt the emitter — a bug in the\r\n * host's UI code should never take the call engine down mid-call. Throws are routed to `onError` so\r\n * the engine can log them without swallowing them silently.\r\n */\r\n// Unconstrained, matching TypedEmitter: the event maps are keyed by enum members (CallEvent,\r\n// SoftphoneEvent), which are closed object types, not string index signatures.\r\nexport class Emitter<M> implements TypedEmitter<M> {\r\n // A copy of the listener set is iterated on emit, so a listener that unsubscribes (or subscribes)\r\n // during dispatch does not mutate the set mid-iteration.\r\n readonly #listeners = new Map<keyof M, Set<(payload: never) => void>>();\r\n\r\n constructor(private readonly onListenerError?: (event: keyof M, error: unknown) => void) {}\r\n\r\n on<E extends keyof M>(event: E, listener: (payload: M[E]) => void): () => void {\r\n let set = this.#listeners.get(event);\r\n if (!set) {\r\n set = new Set();\r\n this.#listeners.set(event, set);\r\n }\r\n set.add(listener as (payload: never) => void);\r\n return () => this.off(event, listener);\r\n }\r\n\r\n once<E extends keyof M>(event: E, listener: (payload: M[E]) => void): () => void {\r\n const wrapped = (payload: M[E]): void => {\r\n off();\r\n listener(payload);\r\n };\r\n const off = this.on(event, wrapped);\r\n return off;\r\n }\r\n\r\n off<E extends keyof M>(event: E, listener: (payload: M[E]) => void): void {\r\n const set = this.#listeners.get(event);\r\n if (!set) return;\r\n set.delete(listener as (payload: never) => void);\r\n if (set.size === 0) this.#listeners.delete(event);\r\n }\r\n\r\n emit<E extends keyof M>(event: E, payload: M[E]): void {\r\n const set = this.#listeners.get(event);\r\n if (!set) return;\r\n for (const listener of [...set]) {\r\n try {\r\n (listener as (p: M[E]) => void)(payload);\r\n } catch (error) {\r\n // A throwing listener must not abort the dispatch loop or bubble into the engine.\r\n this.onListenerError?.(event, error);\r\n }\r\n }\r\n }\r\n\r\n /** Drop every listener. Called on teardown so a destroyed object leaks nothing. */\r\n clear(): void {\r\n this.#listeners.clear();\r\n }\r\n}\r\n","import type { CallEventMap, TypedEmitter } from './events.js';\r\nimport type { CallDirection, RemoteParty } from './types.js';\r\n\r\n/**\r\n * THE call state machine. Singular.\r\n *\r\n * Today there are three competing representations (SPEC §5.3):\r\n * - `callStateRef` — a ref, read synchronously in guards to dodge stale closures\r\n * - `status` — a useState that can lag a render behind it\r\n * - `consultStatus` — the attended-transfer leg, tracked separately\r\n * They can disagree. Collapsing them into one is the single biggest correctness win in this\r\n * refactor, and the consult leg simply becomes its own Call.\r\n *\r\n *\r\n * INBOUND OUTBOUND\r\n * ─────── ────────\r\n * ringing ──answer()──► answering dialing ──180/183──► early\r\n * │ │ │ │\r\n * │ ▼ └────────┬───────────┘\r\n * │ reject() ┌───────┐ │ 200 OK\r\n * └──────────────┐ │ ▼ ▼\r\n * │ │ active ◄─────────────────────\r\n * │ │ ▲ │ ▲ │\r\n * │ │ │ │ │ └──hold()/remote──► held ──unhold()──┐\r\n * │ │ │ │ └───────────────────────────────────────-┘\r\n * │ │ │ └──ICE fail──► reconnecting ──recovered──┐\r\n * │ │ └────────────────────────────────────────────┘\r\n * ▼ ▼ │\r\n * ┌──────────────▼────┐\r\n * │ ended │ TERMINAL. Reachable from every state.\r\n * └────────────────────┘\r\n *\r\n * NOT modelled here, deliberately:\r\n *\r\n * - **`rejecting`.** Today's engine has one, purely to swallow Zadarma's `return_timeout` retries\r\n * for 35 s after a decline (SPEC §3.4). That is a PROVIDER quirk — it belongs in\r\n * `providers/zadarma.ts` as `quirks.rejectGuardMs`, not in the shared state machine. A rejected\r\n * call is simply `ended`. Our own SIP infrastructure would not need it at all (SPEC §2.3).\r\n *\r\n * - **A pre-INVITE state.** A Call exists only once an INVITE has landed. The \"user answered\r\n * before the INVITE arrived\" case (SPEC §3.1) is an *intent* held on `Softphone`, above the\r\n * call — because there is no call yet to hold it. Same for a pre-INVITE reject, which registers\r\n * a UA specifically so it has something to send the 486 from.\r\n */\r\nexport type CallState =\r\n /** Inbound, INVITE received, not yet answered. */\r\n | 'ringing'\r\n /** Outbound, INVITE sent, no provisional response yet. */\r\n | 'dialing'\r\n /** Outbound, remote is ringing (180/183). May carry early media. */\r\n | 'early'\r\n /** Local answer in flight — 200 OK not yet confirmed. */\r\n | 'answering'\r\n /** Media flowing. */\r\n | 'active'\r\n /** Real SIP hold (`a=sendonly`), by either party. */\r\n | 'held'\r\n /** Media lost, ICE restart in flight. The call is NOT over. */\r\n | 'reconnecting'\r\n /** Terminal. */\r\n | 'ended';\r\n\r\nexport type HoldOriginator = 'local' | 'remote';\r\n\r\nexport type HangupReason =\r\n | 'local'\r\n | 'remote'\r\n | 'rejected'\r\n | 'busy'\r\n | 'no-answer'\r\n | 'transferred'\r\n | 'failed';\r\n\r\n/**\r\n * The legal transitions, as data.\r\n *\r\n * A table rather than scattered `if`s: it is testable in isolation, it is the thing a reviewer can\r\n * actually check against the diagram above, and an illegal transition becomes a caught bug instead\r\n * of a silently wrong UI.\r\n */\r\nexport const CALL_TRANSITIONS = {\r\n ringing: ['answering', 'ended'],\r\n dialing: ['early', 'active', 'ended'],\r\n early: ['active', 'ended'],\r\n answering: ['active', 'ended'],\r\n active: ['held', 'reconnecting', 'ended'],\r\n held: ['active', 'reconnecting', 'ended'],\r\n reconnecting: ['active', 'held', 'ended'],\r\n ended: [],\r\n} as const satisfies Readonly<Record<CallState, readonly CallState[]>>;\r\n\r\n/** The states a call can start in. Everything else is reached by transition. */\r\nexport const INITIAL_STATES = {\r\n inbound: 'ringing',\r\n outbound: 'dialing',\r\n} as const satisfies Readonly<Record<CallDirection, CallState>>;\r\n\r\nexport function canTransition(from: CallState, to: CallState): boolean {\r\n return (CALL_TRANSITIONS[from] as readonly CallState[]).includes(to);\r\n}\r\n\r\nexport function isTerminal(state: CallState): boolean {\r\n return CALL_TRANSITIONS[state].length === 0;\r\n}\r\n\r\n/**\r\n * A single call. Exists only while there is one — there is no `idle`.\r\n *\r\n * The attended-transfer consult leg is *also* a Call. That is what lets one state machine cover\r\n * both, instead of today's separate `consultStatus`.\r\n *\r\n * Implementation lands in S2 (against MockProvider) and S3–S4 (against JsSIP/Zadarma).\r\n */\r\nexport interface Call extends TypedEmitter<CallEventMap> {\r\n readonly id: string;\r\n readonly direction: CallDirection;\r\n readonly state: CallState;\r\n readonly remote: RemoteParty;\r\n /** Wall-clock start of media. Null until `active`. */\r\n readonly answeredAt: Date | null;\r\n\r\n readonly isMuted: boolean;\r\n readonly isOnHold: boolean;\r\n\r\n /**\r\n * The remote party's audio stream, once media is flowing (null before). Attach it to an\r\n * `<audio autoplay>` element to hear the call. Also delivered via `CallEvent.RemoteStream`.\r\n */\r\n readonly remoteStream: MediaStream | null;\r\n\r\n /** Only legal from `ringing`. Throws `call/not-answerable` otherwise. */\r\n answer(): Promise<void>;\r\n /** Only legal from `ringing`. Sends 486 Busy Here. */\r\n reject(): Promise<void>;\r\n /** Legal from any non-terminal state. Idempotent. */\r\n hangup(): Promise<void>;\r\n\r\n mute(muted: boolean): void;\r\n\r\n /**\r\n * Real SIP hold — a re-INVITE with `a=sendonly`, so the PBX can play hold music.\r\n *\r\n * Today this is FAKED by muting (SPEC §5.6): the held caller hears dead silence and cannot tell\r\n * hold from a dropped call. JsSIP implements the real thing natively, so this is a handful of\r\n * lines rather than the SIP work it looks like.\r\n */\r\n hold(): Promise<void>;\r\n unhold(): Promise<void>;\r\n\r\n sendDigits(digits: string): void;\r\n\r\n /**\r\n * Blind transfer (SIP REFER). Ends this call — but **whether the transfer itself succeeded or\r\n * failed is unknowable from here**: against Zadarma, a REFER's outcome is not reported back to the\r\n * browser at all (confirmed on real calls — `docs/specs/transfer-availability.md` §2), so a REFER to\r\n * an unreachable target ends this call exactly the same way a successful one does. Prefer\r\n * `startConsult`/`completeTransfer` (below) when the caller must not be put at risk — that path\r\n * correctly reports busy/no-answer/failed because the consult leg is an ordinary outbound call, not\r\n * a REFER.\r\n */\r\n transfer(target: string): Promise<void>;\r\n\r\n /**\r\n * Attended transfer, part 1: call the target while holding this call.\r\n *\r\n * Returns the consult leg — itself a Call, with its own state machine and events. Unlike blind\r\n * `transfer()`, a consult leg that goes unanswered reports a real reason (busy/no-answer/rejected/\r\n * failed) and never puts this call at risk — see `docs/specs/transfer-availability.md` §5.\r\n */\r\n startConsult(target: string): Promise<Call>;\r\n /**\r\n * Attended transfer, part 2: bridge the two, ending both. (REFER … Replaces)\r\n *\r\n * **Confirmed non-functional against Zadarma, on real calls, three times\r\n * (`docs/specs/transfer-availability.md` §12.3).** Zadarma accepts the REFER…Replaces but never\r\n * actually bridges the two legs — the consult leg is silently orphaned and the caller is lost, with\r\n * no error thrown here to signal it. The pattern that DOES work (the same one `/ui`'s own\r\n * `ConsultScreen` uses): hang up the consult leg, then call `transfer(target)` on THIS call, to the\r\n * same already-confirmed-answering target — a blind transfer to a target already known to be there\r\n * works, even though `completeTransfer()` itself does not. Left in the type for hosts against a\r\n * different (non-Zadarma) SIP provider where REFER…Replaces may work as specified; do not reach for\r\n * it against Zadarma.\r\n */\r\n completeTransfer(): Promise<void>;\r\n /** Abandon the consult and resume this call. */\r\n cancelConsult(): Promise<void>;\r\n}\r\n","import { Emitter } from './emitter.js';\r\nimport { CallEvent, type CallEventMap } from './events.js';\r\nimport {\r\n INITIAL_STATES,\r\n canTransition,\r\n isTerminal,\r\n type Call,\r\n type CallState,\r\n type HangupReason,\r\n} from './call.js';\r\nimport type { EngineCall, EngineCallEvent } from './engine.js';\r\nimport type { Contact, RemoteParty } from './types.js';\r\n\r\n/**\r\n * The concrete call. Owns the state machine (SPEC §5.3) and the public event stream.\r\n *\r\n * It never talks to SIP or the provider — it talks to an `EngineCall`, translating that engine's\r\n * RAW signals into public state transitions in one place. The mock's scripted signals and the real\r\n * engine's provider-confirmed ones travel the identical path, which is what keeps the playground\r\n * honest against production.\r\n */\r\nexport class CallImpl extends Emitter<CallEventMap> implements Call {\r\n #state: CallState;\r\n #remote: RemoteParty;\r\n #answeredAt: Date | null = null;\r\n #muted = false;\r\n #onHold = false;\r\n #remoteStream: MediaStream | null = null;\r\n #consultEngineCall: EngineCall | null = null;\r\n #consultCall: Call | null = null;\r\n readonly #unsubscribe: () => void;\r\n\r\n readonly #onListenerError: ((event: keyof CallEventMap, error: unknown) => void) | undefined;\r\n\r\n constructor(\r\n private readonly engineCall: EngineCall,\r\n onListenerError?: (event: keyof CallEventMap, error: unknown) => void,\r\n private readonly onInternalError?: (message: string) => void,\r\n /**\r\n * Fired after `transfer()` resolves, with the target and the ORIGINAL caller's number — an\r\n * internal seam for the softphone's transfer-outcome watch (Phase A,\r\n * `docs/specs/transfer-outcome-relay.md`), never part of the public `Call` interface. Not\r\n * consulted for `startConsult`/`completeTransfer`: those legs already have their own real\r\n * `HangupReason`s (SPEC §5.6) and need no external polling to know what happened to them.\r\n */\r\n private readonly onTransfer?: (target: string, callerNumber: string | null) => void,\r\n ) {\r\n super(onListenerError);\r\n this.#onListenerError = onListenerError;\r\n this.#state = INITIAL_STATES[engineCall.direction];\r\n this.#remote = {\r\n number: engineCall.remoteNumber,\r\n displayName: engineCall.remoteNumber,\r\n contact: null,\r\n };\r\n this.#unsubscribe = engineCall.onEvent((event) => this.#onEngineEvent(event));\r\n }\r\n\r\n get id(): string {\r\n return this.engineCall.id;\r\n }\r\n get direction() {\r\n return this.engineCall.direction;\r\n }\r\n get state(): CallState {\r\n return this.#state;\r\n }\r\n get remote(): RemoteParty {\r\n return this.#remote;\r\n }\r\n get answeredAt(): Date | null {\r\n return this.#answeredAt;\r\n }\r\n get isMuted(): boolean {\r\n return this.#muted;\r\n }\r\n get isOnHold(): boolean {\r\n return this.#onHold;\r\n }\r\n get remoteStream(): MediaStream | null {\r\n return this.#remoteStream;\r\n }\r\n\r\n // ── Raw engine signals → state machine ──────────────────────────────────────\r\n #onEngineEvent(event: EngineCallEvent): void {\r\n switch (event.type) {\r\n case 'progress':\r\n this.#transition('early');\r\n break;\r\n case 'accepted':\r\n if (this.#answeredAt === null) this.#answeredAt = this.#now();\r\n this.#transition('active');\r\n this.emit(CallEvent.Accepted, undefined);\r\n break;\r\n case 'held':\r\n this.#onHold = true;\r\n if (this.#transition('held')) this.emit(CallEvent.Held, { originator: event.originator });\r\n break;\r\n case 'unheld':\r\n this.#onHold = false;\r\n if (this.#transition('active')) this.emit(CallEvent.Unheld, { originator: event.originator });\r\n break;\r\n case 'reconnecting':\r\n if (this.#transition('reconnecting')) this.emit(CallEvent.Reconnecting, undefined);\r\n break;\r\n case 'reconnected':\r\n if (this.#transition(this.#onHold ? 'held' : 'active'))\r\n this.emit(CallEvent.Reconnected, undefined);\r\n break;\r\n case 'ended':\r\n this.#end(event.reason);\r\n break;\r\n case 'stream':\r\n // Expose the remote audio so the host can play it. Core must not create an <audio> element\r\n // itself (SPEC §10.2) — it hands the stream out and the UI/host attaches it.\r\n this.#remoteStream = event.stream;\r\n this.emit(CallEvent.RemoteStream, { stream: event.stream });\r\n break;\r\n }\r\n }\r\n\r\n // ── Commands ────────────────────────────────────────────────────────────────\r\n async answer(): Promise<void> {\r\n if (this.#state !== 'ringing') {\r\n throw this.#stateError('answer', 'call/not-answerable');\r\n }\r\n // Move to `answering` BEFORE awaiting the engine, so a fast provider \"answered\" signal cannot\r\n // race past the ringing state (SPEC §3.1). The engine confirms with an `accepted` event, which\r\n // is what actually reaches `active`.\r\n this.#transition('answering');\r\n await this.engineCall.answer();\r\n }\r\n\r\n async reject(): Promise<void> {\r\n if (this.#state !== 'ringing') {\r\n throw this.#stateError('reject', 'call/not-answerable');\r\n }\r\n await this.engineCall.reject();\r\n // The engine emits `ended` with reason 'rejected' — no local transition here, so there is one\r\n // path to `ended`.\r\n }\r\n\r\n async hangup(): Promise<void> {\r\n if (isTerminal(this.#state)) return; // idempotent\r\n // A live consult leg (SPEC §5.6) is a REAL second call to the transfer target — hanging up on the\r\n // caller without also ending it would leave that extension ringing forever, orphaned, with nothing\r\n // left holding a reference to it once this call reaches `ended`. Ending it HERE (rather than only\r\n // relying on `#end()`'s own safety net below) fires both hangups in parallel instead of waiting for\r\n // this call's own round trip to complete first — `#end()` still needs its copy of this for every\r\n // OTHER way the main call can end (the caller hanging up first, an engine failure, anything that\r\n // never calls this method at all).\r\n const consult = this.#consultCall;\r\n this.#consultCall = null;\r\n this.#consultEngineCall = null;\r\n await Promise.all([consult?.hangup() ?? Promise.resolve(), this.engineCall.hangup()]);\r\n }\r\n\r\n mute(muted: boolean): void {\r\n if (this.#muted === muted) return;\r\n this.#muted = muted;\r\n this.engineCall.mute(muted);\r\n this.emit(CallEvent.Muted, { muted });\r\n }\r\n\r\n async hold(): Promise<void> {\r\n if (this.#onHold || (this.#state !== 'active' && this.#state !== 'reconnecting')) return;\r\n await this.engineCall.hold();\r\n // The engine confirms with a `held` event (originator: 'local').\r\n }\r\n\r\n async unhold(): Promise<void> {\r\n if (!this.#onHold) return;\r\n await this.engineCall.unhold();\r\n }\r\n\r\n sendDigits(digits: string): void {\r\n this.engineCall.sendDigits(digits);\r\n this.emit(CallEvent.DigitsSent, { digits });\r\n }\r\n\r\n async transfer(target: string): Promise<void> {\r\n await this.engineCall.transfer(target);\r\n // Blind transfer ends this call; the engine emits `ended` with reason 'transferred'.\r\n this.onTransfer?.(target, this.#remote.number.trim() || null);\r\n }\r\n\r\n async startConsult(target: string): Promise<Call> {\r\n // Put the caller on REAL hold (a=sendonly → the PBX plays hold music) before dialling the\r\n // consult target — otherwise they hear the agent's consult conversation. The legacy softphone\r\n // FAKED this by muting, so the caller heard dead silence (SPEC §5.6); real hold is the fix.\r\n await this.hold();\r\n\r\n const consultEngineCall = await this.engineCall.startConsult(target);\r\n // The consult leg is a first-class Call with its own state machine — this is what replaces\r\n // today's separate `consultStatus` (SPEC §5.3). We keep the underlying engine handle so\r\n // completeTransfer can bridge the two legs (REFER…Replaces).\r\n this.#consultEngineCall = consultEngineCall;\r\n const consult = new CallImpl(consultEngineCall, this.#onListenerError, this.onInternalError);\r\n this.#consultCall = consult;\r\n return consult;\r\n }\r\n\r\n async completeTransfer(): Promise<void> {\r\n if (!this.#consultEngineCall) throw this.#stateError('completeTransfer', 'call/unsupported');\r\n // The bridge (REFER…Replaces) ends both legs; the caller leaves hold by being connected to the\r\n // target, so there is nothing to unhold — the engine emits `ended` on each.\r\n await this.engineCall.completeTransfer(this.#consultEngineCall);\r\n }\r\n\r\n async cancelConsult(): Promise<void> {\r\n if (!this.#consultCall) return;\r\n await this.#consultCall.hangup();\r\n this.#consultCall = null;\r\n this.#consultEngineCall = null;\r\n // Take the caller off hold — the agent is coming back to them.\r\n await this.unhold();\r\n }\r\n\r\n // ── Internals ───────────────────────────────────────────────────────────────\r\n #transition(to: CallState): boolean {\r\n const from = this.#state;\r\n if (from === to) return false;\r\n if (isTerminal(from)) return false; // never leave `ended`\r\n if (!canTransition(from, to)) {\r\n this.onInternalError?.(`illegal call transition ${from} → ${to} (SPEC §5.3)`);\r\n return false;\r\n }\r\n this.#state = to;\r\n this.emit(CallEvent.StateChanged, { state: to, previous: from });\r\n return true;\r\n }\r\n\r\n #end(reason: HangupReason): void {\r\n if (isTerminal(this.#state)) return;\r\n // Safety net for every path that ends THIS call without going through `hangup()` above — the\r\n // caller hanging up on their own while a consult is in progress, an engine failure, anything the\r\n // provider reports as `ended`. Found live (`docs/specs/transfer-availability.md`): without this,\r\n // only an agent-initiated hangup ever cleaned up the consult leg, so the caller hanging up first\r\n // left the transfer target on a real, ringing-or-answered call with nobody left holding a\r\n // reference to it — the exact \"ghost call\" this method's sibling was written to prevent, just\r\n // reachable from a different direction. `hangup()`'s own copy still runs first when it applies —\r\n // by the time its `engineCall.hangup()` round-trips back here, `#consultCall` is already `null`,\r\n // so this is a no-op in that case, not a double-hangup.\r\n if (this.#consultCall) {\r\n const consult = this.#consultCall;\r\n this.#consultCall = null;\r\n this.#consultEngineCall = null;\r\n void consult.hangup();\r\n }\r\n const from = this.#state;\r\n this.#state = 'ended';\r\n this.emit(CallEvent.StateChanged, { state: 'ended', previous: from });\r\n this.emit(CallEvent.Ended, { reason });\r\n this.#unsubscribe();\r\n this.clear();\r\n }\r\n\r\n #stateError(op: string, _code: 'call/not-answerable' | 'call/unsupported'): Error {\r\n // A real SoftphoneError is minted in S2's errors implementation; a plain Error keeps the type\r\n // surface honest until then.\r\n return new Error(`${op}() is not valid while call is '${this.#state}'`);\r\n }\r\n\r\n #now(): Date {\r\n // Isolated so tests can stub it, and so we never reach for a bare `new Date()` in logic.\r\n return new Date();\r\n }\r\n\r\n /** @internal — the Softphone updates caller identity once `resolveContact` resolves. */\r\n setRemoteContact(contact: Contact): void {\r\n this.#remote = {\r\n number: this.#remote.number,\r\n displayName: contact.name ?? this.#remote.number,\r\n contact,\r\n };\r\n this.emit(CallEvent.RemotePartyUpdated, undefined);\r\n }\r\n}\r\n","import type {\r\n AgentsDirectory,\r\n Contact,\r\n ContactsSource,\r\n CoventiaBackendConfig,\r\n HistorySource,\r\n PresenceSource,\r\n TokenSource,\r\n TransferOutcomesPage,\r\n} from './types.js';\r\n\r\n/** Contacts, history, caller ID, the agents directory, transfer outcomes and presence, all from one\r\n * config. */\r\nexport interface BackendSources {\r\n readonly contacts: ContactsSource;\r\n readonly history: HistorySource;\r\n readonly resolveContact: (number: string) => Promise<Contact | null>;\r\n /** The Historial tab's \"filter by person\" directory. Always Coventia's own org membership. */\r\n readonly listAgents: () => Promise<AgentsDirectory>;\r\n /** Phase A (transfer outcome relay) — polled by the transfer watch, never by UI directly. */\r\n readonly transferOutcomes: (params: { readonly since?: string }) => Promise<TransferOutcomesPage>;\r\n /** Phase B (presence-gated transfer picker) — opened by `presence-socket.ts`, never by UI directly. */\r\n readonly presence: PresenceSource;\r\n}\r\n\r\n/**\r\n * Coventia's phone service, wired from `SoftphoneConfig.coventia`.\r\n *\r\n * **Synchronous object, lazily-loaded module.** The widget needs to know *at first render* whether\r\n * the Contactos and Historial tabs exist, and that is answered by the config alone — so this returns\r\n * immediately, while the ~2 kB adapter is fetched on first use. Core's own entry therefore carries no\r\n * HTTP and no `wa_id`, the same trick used for `jssip`, the flag SVGs and the phone-number metadata.\r\n *\r\n * One instance per softphone: the widget reads it back off `softphone.sources`, so it never builds a\r\n * second set with its own name cache.\r\n */\r\nexport function coventiaBackend(\r\n config: CoventiaBackendConfig,\r\n tokenSource: TokenSource,\r\n /** `SoftphoneConfig.apiBase`, consulted only when `config.baseUrl` is not set. */\r\n fallbackBaseUrl?: string,\r\n): BackendSources {\r\n // Resolved and validated EAGERLY, at `createSoftphone()` time, not deferred into `load()` below —\r\n // a host who configured `coventia` clearly wants contacts/history/caller ID, so a missing base URL\r\n // is a real misconfiguration, and the worst place to surface one is the first time a user opens\r\n // the Contactos tab, long after the phone has been happily reporting itself online.\r\n const baseUrl = config.baseUrl ?? fallbackBaseUrl;\r\n if (!baseUrl) {\r\n throw new Error(\r\n 'No base URL for the Coventia backend. Pass `coventia.baseUrl`, or a top-level `apiBase` on ' +\r\n '`createSoftphone` for it to fall back to.',\r\n );\r\n }\r\n\r\n let pending: Promise<BackendSources> | null = null;\r\n const load = (): Promise<BackendSources> => {\r\n pending ??= import('../coventia/sources.js').then((m) =>\r\n m.coventiaSources({ ...config, baseUrl, tokenSource }),\r\n );\r\n return pending;\r\n };\r\n\r\n return {\r\n contacts: async (params) => (await load()).contacts(params),\r\n history: async (params) => (await load()).history(params),\r\n resolveContact: async (number) => (await load()).resolveContact(number),\r\n listAgents: async () => (await load()).listAgents(),\r\n transferOutcomes: async (params) => (await load()).transferOutcomes(params),\r\n presence: async (callbacks) => (await load()).presence(callbacks),\r\n };\r\n}\r\n","import { isBrowser } from './env.js';\r\n\r\n/**\r\n * Cross-tab leader election (SPEC §5.2, §5.3; `multi-tab-leader-election.md`).\r\n *\r\n * Zadarma's on-demand registration means every open tab that reacts to the same push independently\r\n * registers a SIP UA for the same extension — a race the SDK never controlled, because nothing told\r\n * one tab another existed. Confirmed live: with two tabs open, an inbound call rings in exactly one,\r\n * unpredictably which. `TabLock` is the seam that lets exactly one tab (\"the leader\") ever start the\r\n * real engine; every other tab (\"a follower\") stays dormant until its turn comes.\r\n */\r\nexport interface TabLock {\r\n /**\r\n * Resolves once this tab becomes leader — immediately if no other tab currently leads (the common\r\n * case: a single open tab, or an environment without Web Locks), or later, whenever (if ever)\r\n * another tab releases it. Never rejects; a cancelled attempt (via `release()`) simply never\r\n * resolves.\r\n */\r\n acquireLeadership(): Promise<void>;\r\n\r\n /** Relinquish leadership if held, or cancel a still-pending attempt. Safe to call more than once. */\r\n release(): void;\r\n}\r\n\r\nconst LOCK_NAME = 'coventia-softphone:leader';\r\n\r\n/**\r\n * True when the browser exposes the Web Locks API this coordination depends on.\r\n *\r\n * Safari added support in 15.4 (2022); Chrome and Firefox both predate that comfortably. Where it's\r\n * missing, `webLocksTabLock()` grants leadership immediately to every tab — today's actual\r\n * (uncoordinated, but functional) behaviour, not a regression.\r\n */\r\nexport function isWebLocksSupported(): boolean {\r\n return isBrowser() && typeof navigator.locks?.request === 'function';\r\n}\r\n\r\n/**\r\n * The real implementation. Constructed lazily by a caller, never at module scope (SPEC §10.3) — safe\r\n * to import on a server, and an environment without Web Locks pays nothing beyond one property read.\r\n *\r\n * One `TabLock` is meant to be acquired at most once per `Softphone` lifetime, from `start()` — see\r\n * `multi-tab-leader-election.md` §3.4.\r\n */\r\nexport function webLocksTabLock(lockName: string = LOCK_NAME): TabLock {\r\n const controller = new AbortController();\r\n let releaseHold: (() => void) | undefined;\r\n\r\n return {\r\n acquireLeadership(): Promise<void> {\r\n if (!isWebLocksSupported()) return Promise.resolve();\r\n\r\n return new Promise<void>((resolveAcquired) => {\r\n navigator.locks\r\n .request(lockName, { signal: controller.signal }, () => {\r\n // Granted — tell the caller NOW. How long the browser keeps the lock is governed by the\r\n // SEPARATE promise below, resolved later by release(), not by this one.\r\n resolveAcquired();\r\n return new Promise<void>((resolveHold) => {\r\n releaseHold = resolveHold;\r\n });\r\n })\r\n .catch(() => {\r\n // Aborted by release() before our turn came — expected cancellation, not a failure.\r\n // `acquireLeadership()`'s promise simply never resolves in this case.\r\n });\r\n });\r\n },\r\n\r\n release(): void {\r\n if (releaseHold) {\r\n const resolve = releaseHold;\r\n releaseHold = undefined;\r\n resolve();\r\n } else {\r\n controller.abort();\r\n }\r\n },\r\n };\r\n}\r\n","import type { TransferOutcomeEvent, TransferOutcomeSource, TransferOutcomesPage } from './types.js';\r\n\r\n/**\r\n * Poll cadence (spec §5.4, plan T5): 2 s for the first 10 s — a real success answers within ~4 s\r\n * (spec §8, F5) — then 5 s until the deadline. The observed no-answer ring is ~61 s (spec §8, F4); 90 s\r\n * leaves headroom without polling indefinitely.\r\n */\r\nconst FAST_INTERVAL_MS = 2_000;\r\nconst FAST_WINDOW_MS = 10_000;\r\nconst SLOW_INTERVAL_MS = 5_000;\r\nconst DEADLINE_MS = 90_000;\r\n\r\n/**\r\n * Digits only, at most six — deliberately generous next to Zadarma's own 3-digit extensions (spec §7,\r\n * plan §2). A `+`-prefixed or longer number is an outside line: no `NOTIFY_INTERNAL` will ever name it,\r\n * so there is nothing to poll for and starting a watch would just waste requests until the deadline.\r\n */\r\nconst EXTENSION_LIKE = /^\\d{1,6}$/;\r\n\r\nexport interface TransferWatchParams {\r\n readonly target: string;\r\n /** The ORIGINAL caller's number, for the eventual `TransferOutcomeEvent.caller` — null if unknown. */\r\n readonly callerNumber: string | null;\r\n readonly source: TransferOutcomeSource;\r\n /** Re-checked on every poll, not just at the start — see the leadership-handover note below. */\r\n readonly getIsLeader: () => boolean;\r\n readonly emit: (payload: TransferOutcomeEvent) => void;\r\n /** Overridable for tests; default to the real timers/clock. */\r\n readonly setTimeout?: typeof globalThis.setTimeout;\r\n readonly clearTimeout?: typeof globalThis.clearTimeout;\r\n readonly now?: () => number;\r\n}\r\n\r\n/**\r\n * Starts polling for one transfer's outcome (spec §5.4, plan T5). Returns a `stop()` — called from\r\n * `destroy()`, and safe to call again or after the watch already finished on its own (idempotent).\r\n *\r\n * **Each call owns its own private polling loop.** Deliberately not a single shared, mutable watch the\r\n * softphone reuses: `Call.transfer()` ends the call it was called on, so an agent is free to place and\r\n * transfer a SECOND call before the first watch's 90 s window elapses — multiple watches must be able\r\n * to run at once without one clobbering another's cursor or state. `no cross-attribution` is what this\r\n * buys, for the case the plan's acceptance actually describes: CONSECUTIVE transfers to the same\r\n * target, one fully resolved before the next begins. (Two watches racing for the exact same target at\r\n * the exact same time is a narrower case this design does not additionally solve — see the matching\r\n * step below.)\r\n */\r\nexport function startTransferWatch(params: TransferWatchParams): () => void {\r\n const {\r\n target,\r\n callerNumber,\r\n source,\r\n getIsLeader,\r\n emit,\r\n setTimeout: scheduleTimeout = globalThis.setTimeout,\r\n clearTimeout: cancelTimeout = globalThis.clearTimeout,\r\n now = () => Date.now(),\r\n } = params;\r\n\r\n // Outside line — nothing will ever be reported for it (spec §7). Checked before anything else so a\r\n // typed phone number never starts a single request.\r\n if (!EXTENSION_LIKE.test(target)) return () => {};\r\n // A follower tab never actually holds the call that just transferred (LeaderGate never lets one\r\n // place or receive a call at all) — this should be unreachable in practice, but costs nothing to\r\n // state explicitly rather than rely on that being true by construction elsewhere.\r\n if (!getIsLeader()) return () => {};\r\n\r\n let stopped = false;\r\n let timer: ReturnType<typeof globalThis.setTimeout> | null = null;\r\n let cursor: string | null = null;\r\n const startedAt = now();\r\n\r\n const stop = (): void => {\r\n stopped = true;\r\n if (timer !== null) {\r\n cancelTimeout(timer);\r\n timer = null;\r\n }\r\n };\r\n\r\n const finish = (status: 'connected' | 'not-connected'): void => {\r\n stop();\r\n emit({ status, target, caller: callerNumber, at: new Date(now()) });\r\n };\r\n\r\n const scheduleNext = (): void => {\r\n if (stopped) return;\r\n const elapsed = now() - startedAt;\r\n if (elapsed >= DEADLINE_MS) {\r\n // Deadline — stop silently. Spec §5.4: \"a timeout is never reported as success or failure.\"\r\n stop();\r\n return;\r\n }\r\n timer = scheduleTimeout(() => void poll(), elapsed < FAST_WINDOW_MS ? FAST_INTERVAL_MS : SLOW_INTERVAL_MS);\r\n };\r\n\r\n const poll = async (): Promise<void> => {\r\n if (stopped) return;\r\n if (!getIsLeader()) {\r\n // Leadership changed mid-watch (a tab handover) — this tab no longer holds the call that\r\n // transferred. Stop rather than keep polling and risk surfacing a notice nowhere it belongs.\r\n stop();\r\n return;\r\n }\r\n\r\n let page: TransferOutcomesPage;\r\n try {\r\n page = await source(cursor === null ? {} : { since: cursor });\r\n } catch {\r\n // A failed poll never ends the watch and never reaches the user or the host (plan T5's own\r\n // acceptance) — the transfer itself didn't fail, only this one attempt to check on it. Retry on\r\n // the normal cadence rather than inventing a separate backoff the spec doesn't call for.\r\n if (!stopped) scheduleNext();\r\n return;\r\n }\r\n if (stopped) return; // stop() may have run while the request was in flight\r\n\r\n if (cursor === null) {\r\n // The FIRST poll exists only to learn the starting cursor (spec §5.3's no-mis-attribution rule)\r\n // — anything already in the inbox predates this transfer and is deliberately never inspected.\r\n cursor = page.cursor;\r\n } else {\r\n cursor = page.cursor;\r\n const match = page.outcomes.find((o) => o.target === target);\r\n if (match) {\r\n finish(match.status);\r\n return;\r\n }\r\n }\r\n\r\n scheduleNext();\r\n };\r\n\r\n void poll(); // fires immediately — no reason to wait 2 s just to learn the starting cursor\r\n return stop;\r\n}\r\n","import type { PresenceEntry, PresenceSource } from './types.js';\r\n\r\n/**\r\n * Reconnect backoff (plan T6, spec §4.4/§4.7): doubling from 1 s, capped at 30 s, jittered so a\r\n * Cloud Run-wide cut (spec §4.2 — every connection capped at 60 min) doesn't send every agent's\r\n * softphone back in at the same instant. Not configurable — nothing about a specific value is part\r\n * of any contract, only \"backoff and jitter\" is (spec §4.4).\r\n */\r\nconst RECONNECT_BASE_MS = 1_000;\r\nconst RECONNECT_MAX_MS = 30_000;\r\n\r\nexport interface StartPresenceSocketParams {\r\n readonly source: PresenceSource;\r\n /**\r\n * Checked before the FIRST connection attempt, and again before every reconnect attempt after a\r\n * drop — the same two decision points `transfer-watch.ts`'s own `getIsLeader` is checked at (\"at\r\n * the start\" and \"on every poll\"). A persistent connection has no poll tick to hang a recheck off,\r\n * so those two moments — first attempt, each retry — are this module's equivalent: the places it is\r\n * already about to do something, rather than an invented timer whose only job would be to ask.\r\n *\r\n * Today's `SoftphoneImpl` grants leadership once, in `start()`, and only ever revokes it in\r\n * `stop()`/`destroy()` (`tabs.ts` — \"acquired at most once per Softphone lifetime\") — so in\r\n * practice `startPresenceSocket` is only ever called once leadership already holds, and this never\r\n * flips back to false out from under a running connection. Checked anyway, for the same reason\r\n * `transfer-watch.ts` does: cheap to state explicitly, and the caller who wires this up (plan T7)\r\n * is expected to call `stop()` the moment leadership or online-ness is lost, same as `#onTransfer`\r\n * already does for `#activeWatches` — this is a second line of defence, not the primary one.\r\n */\r\n readonly getIsLeader: () => boolean;\r\n /**\r\n * Fires after `snapshot` (the list, wholesale) and after every applied `delta` (the list, already\r\n * merged) — never the raw wire message. A caller wanting \"just this one agent\" over-reads a single\r\n * field; wanting \"everyone right now\" is the only shape a picker (T7) ever actually needs.\r\n */\r\n readonly onAgentsChanged: (agents: readonly PresenceEntry[]) => void;\r\n /** Overridable for tests; default to the real timers. */\r\n readonly setTimeout?: typeof globalThis.setTimeout;\r\n readonly clearTimeout?: typeof globalThis.clearTimeout;\r\n /** Overridable for tests — deterministic jitter. Defaults to `Math.random`. */\r\n readonly random?: () => number;\r\n}\r\n\r\nexport interface PresenceWatch {\r\n /**\r\n * Report this softphone's own on-call-ness. Safe to call as often as the caller likes — a no-op\r\n * unless the value actually changed since the last report (spec §4.4: \"never on an interval\").\r\n * While disconnected, the latest value is remembered and replayed on the next successful connect —\r\n * a call that started while the socket happened to be reconnecting must not be lost.\r\n */\r\n readonly reportCallState: (onCall: boolean) => void;\r\n /** Stops for good — no further reconnect attempts. Idempotent, matching `startTransferWatch`'s own\r\n * `stop()` and `#activeWatches`'s cleanup convention. */\r\n readonly stop: () => void;\r\n}\r\n\r\nconst NOOP_WATCH: PresenceWatch = { reportCallState: () => {}, stop: () => {} };\r\n\r\n/**\r\n * Starts (or immediately declines to start, for a follower tab) the phone-presence connection (plan\r\n * T6). Framework-free and transport-free: `source` is the only thing that knows how to actually open\r\n * a connection — Coventia's by default (`coventia/sources.ts`: mints a ticket, opens the WebSocket),\r\n * a host's own for a test double. This module owns only what is true of ANY such source: leader-\r\n * gating, reconnect-with-backoff, folding `snapshot`/`delta` into one live list, and de-duplicating\r\n * outgoing call-state reports.\r\n *\r\n * When `source` never resolves (a follower that should never have been given a source at all, or a\r\n * backend that is simply down) this produces no data and raises no error — spec §4.4's own success\r\n * criterion: the picker falls back to free text, nothing more.\r\n */\r\nexport function startPresenceSocket(params: StartPresenceSocketParams): PresenceWatch {\r\n const {\r\n source,\r\n getIsLeader,\r\n onAgentsChanged,\r\n setTimeout: scheduleTimeout = globalThis.setTimeout,\r\n clearTimeout: cancelTimeout = globalThis.clearTimeout,\r\n random = Math.random,\r\n } = params;\r\n\r\n // A follower never opens a connection at all — mirrors `startTransferWatch`'s own\r\n // `if (!getIsLeader()) return () => {}` bail (this file's own doc comment above explains why this\r\n // is the current design's normal, expected path for every tab but one, not an edge case).\r\n if (!getIsLeader()) return NOOP_WATCH;\r\n\r\n let stopped = false;\r\n let attempt = 0;\r\n let reconnectTimer: ReturnType<typeof globalThis.setTimeout> | null = null;\r\n let agents = new Map<string, PresenceEntry>();\r\n let lastReportedOnCall: boolean | null = null;\r\n let connection: Awaited<ReturnType<PresenceSource>> | null = null;\r\n\r\n const stop = (): void => {\r\n if (stopped) return;\r\n stopped = true;\r\n if (reconnectTimer !== null) {\r\n cancelTimeout(reconnectTimer);\r\n reconnectTimer = null;\r\n }\r\n connection?.close();\r\n connection = null;\r\n };\r\n\r\n const scheduleReconnect = (): void => {\r\n if (stopped) return;\r\n attempt += 1;\r\n const capped = Math.min(RECONNECT_BASE_MS * 2 ** (attempt - 1), RECONNECT_MAX_MS);\r\n // Full jitter: anywhere from 0 up to the capped delay, so a mass reconnect (a Cloud Run-wide cut)\r\n // spreads out instead of every socket retrying on the exact same clock tick.\r\n const delay = capped * random();\r\n reconnectTimer = scheduleTimeout(() => void connect(), delay);\r\n };\r\n\r\n const connect = async (): Promise<void> => {\r\n if (stopped) return;\r\n // Re-checked here too (see the param doc above) — the one moment after the very first attempt\r\n // where this module is about to do something and can cheaply ask first.\r\n if (!getIsLeader()) {\r\n stop();\r\n return;\r\n }\r\n\r\n let opened: Awaited<ReturnType<PresenceSource>>;\r\n try {\r\n opened = await source({\r\n onSnapshot: (snapshot) => {\r\n agents = new Map(snapshot.map((agent) => [agent.userId, agent]));\r\n onAgentsChanged([...agents.values()]);\r\n },\r\n onDelta: (agent) => {\r\n agents.set(agent.userId, agent);\r\n onAgentsChanged([...agents.values()]);\r\n },\r\n onClose: () => {\r\n // `connection === null` also covers \"not opened yet\" — safe because `source` is contracted\r\n // to resolve (open) BEFORE it ever calls `onClose` (a real WebSocket's own `open`/`close`\r\n // are always separate ticks; there is no such thing as a connection that closes before it\r\n // opens). A close reported before `connection` is set is therefore always OUR OWN — `stop()`\r\n // discarding this very attempt while it was still in flight — never a genuine drop.\r\n if (connection === null) return;\r\n connection = null;\r\n scheduleReconnect();\r\n },\r\n });\r\n } catch {\r\n // Ticket mint failed, the socket never opened, whatever — indistinguishable from a drop, so it\r\n // gets the same treatment: quiet retry, never surfaced (spec §4.4, success criterion #5).\r\n if (!stopped) scheduleReconnect();\r\n return;\r\n }\r\n\r\n if (stopped) {\r\n // `stop()` ran while the connection was still being opened — close it now that it exists rather\r\n // than leave it live with nothing left to receive its callbacks.\r\n opened.close();\r\n return;\r\n }\r\n\r\n connection = opened;\r\n attempt = 0; // a successful connect resets the backoff for the NEXT drop, not this one\r\n if (lastReportedOnCall !== null) connection.reportCallState(lastReportedOnCall);\r\n };\r\n\r\n void connect();\r\n\r\n return {\r\n reportCallState: (onCall) => {\r\n if (onCall === lastReportedOnCall) return;\r\n lastReportedOnCall = onCall;\r\n connection?.reportCallState(onCall);\r\n },\r\n stop,\r\n };\r\n}\r\n","import { Emitter } from './emitter.js';\r\nimport { CallImpl } from './call-impl.js';\r\nimport { coventiaBackend, type BackendSources } from './backend.js';\r\nimport { cachedTokenSource } from './token-cache.js';\r\nimport { CallEvent, SoftphoneEvent, type SoftphoneEventMap } from './events.js';\r\nimport { isBrowser } from './env.js';\r\nimport { webLocksTabLock, type TabLock } from './tabs.js';\r\nimport { startTransferWatch } from './transfer-watch.js';\r\nimport { startPresenceSocket, type PresenceWatch } from './presence-socket.js';\r\nimport type { AudioDevices, Softphone } from './softphone.js';\r\nimport type { CallEngine, EngineCall } from './engine.js';\r\nimport type { Call } from './call.js';\r\nimport { isFatal, type FatalSoftphoneError, type SoftphoneError } from './errors.js';\r\nimport type { CallOptions, PresenceEntry, SoftphoneConfig, SoftphoneState } from './types.js';\r\n\r\n/**\r\n * A page may hold ONE live softphone. A softphone owns singleton browser resources — the microphone,\r\n * a registration, an RTCPeerConnection — and a second instance means a second `getUserMedia` and two\r\n * registrations fighting over one extension. Daily throws on a second call object for the same\r\n * reason; we follow it.\r\n *\r\n * Module-scope, but not a browser global, so it is SSR-safe (SPEC §10.2): it is a plain boolean that\r\n * flips in `start()`, never at import.\r\n */\r\nlet liveInstance = false;\r\n\r\n/**\r\n * The real device surface (SPEC §7 Phase 2). Live `enumerateDevices()`, reactive to the browser's own\r\n * `devicechange`, input switching delegated to the engine (`CallEngine.setInputDevice` — the SIP\r\n * engine implements it; a mock with nothing real to swap simply omits it), output switching entirely\r\n * local (there is no engine involvement at all — see `AudioDevices.setOutput`'s own comment: routing\r\n * already-decoded audio needs no SIP/WebRTC layer underneath it).\r\n *\r\n * SSR-safe like everything else under `core/` (SPEC §10.2): every browser global is read lazily —\r\n * never at module scope — and gated behind `isBrowser()`.\r\n */\r\nclass BrowserAudioDevices implements AudioDevices {\r\n inputs: readonly MediaDeviceInfo[] = [];\r\n outputs: readonly MediaDeviceInfo[] = [];\r\n currentInputId: string | null = null;\r\n currentOutputId: string | null = null;\r\n readonly canSelectOutput: boolean;\r\n\r\n readonly #engine: CallEngine;\r\n readonly #onChange: () => void;\r\n\r\n constructor(engine: CallEngine, onChange: () => void) {\r\n this.#engine = engine;\r\n this.#onChange = onChange;\r\n this.canSelectOutput =\r\n isBrowser() &&\r\n typeof HTMLMediaElement !== 'undefined' &&\r\n 'setSinkId' in HTMLMediaElement.prototype;\r\n\r\n if (isBrowser() && navigator.mediaDevices) {\r\n void this.refresh();\r\n // No teardown: `mediaDevices` is the FRAME's own object, not a resource this instance opened,\r\n // and this instance lives exactly as long as the Softphone does — there is nothing to leak.\r\n navigator.mediaDevices.addEventListener?.('devicechange', () => void this.refresh());\r\n }\r\n }\r\n\r\n async refresh(): Promise<void> {\r\n if (!isBrowser() || typeof navigator.mediaDevices?.enumerateDevices !== 'function') return;\r\n let all: readonly MediaDeviceInfo[];\r\n try {\r\n all = await navigator.mediaDevices.enumerateDevices();\r\n } catch {\r\n // Rare, but real — a locked-down permissions policy or an odd polyfill can reject this. The\r\n // lists simply stay whatever they were; there is no better fallback than \"unchanged.\"\r\n return;\r\n }\r\n this.inputs = all.filter((d) => d.kind === 'audioinput');\r\n this.outputs = all.filter((d) => d.kind === 'audiooutput');\r\n // A selection pointing at a device that just vanished (unplugged) must not keep claiming it is\r\n // still selected — the UI would show a \"chosen\" option that no longer exists in either list.\r\n if (this.currentInputId && !this.inputs.some((d) => d.deviceId === this.currentInputId)) {\r\n this.currentInputId = null;\r\n }\r\n if (this.currentOutputId && !this.outputs.some((d) => d.deviceId === this.currentOutputId)) {\r\n this.currentOutputId = null;\r\n }\r\n this.#onChange();\r\n }\r\n\r\n async setInput(deviceId: string | null): Promise<void> {\r\n this.currentInputId = deviceId;\r\n this.#onChange();\r\n // Optional on the interface — a mock engine with no real media has nothing to swap and simply\r\n // does not implement it.\r\n await this.#engine.setInputDevice?.(deviceId);\r\n }\r\n\r\n async setOutput(deviceId: string | null): Promise<void> {\r\n this.currentOutputId = deviceId;\r\n this.#onChange();\r\n // Nothing to await here: applying it is the UI layer's job (`useCallAudio`'s `setSinkId` on the\r\n // remote-audio element), reached only through `SoftphoneEvent.DeviceChange` — core/ cannot touch\r\n // `document` itself (SPEC §10.2).\r\n }\r\n\r\n async setRingtoneOutput(): Promise<void> {\r\n /* separate ringtone output lands in a later pass — still Phase 2 groundwork */\r\n }\r\n}\r\n\r\nexport class SoftphoneImpl extends Emitter<SoftphoneEventMap> implements Softphone {\r\n #state: SoftphoneState = 'stopped';\r\n #lastError: FatalSoftphoneError | null = null;\r\n #activeCall: CallImpl | null = null;\r\n #destroyed = false;\r\n #startedHere = false;\r\n #isLeader = false;\r\n readonly #engine: CallEngine;\r\n readonly #tabLock: TabLock;\r\n readonly #resolveContact: SoftphoneConfig['resolveContact'];\r\n /** Contacts + history, whenever there is a Coventia backend to reach. The widget reads these. */\r\n readonly sources: BackendSources | null;\r\n readonly audio: AudioDevices;\r\n /**\r\n * Every in-flight transfer-outcome watch (Phase A), so `destroy()` can stop all of them. Never\r\n * pruned as individual watches finish on their own — `stop()` is idempotent and cheap to call again,\r\n * and a session accumulating a handful of closures for however many transfers it makes is not worth\r\n * the bookkeeping a self-removing set would add.\r\n */\r\n readonly #activeWatches = new Set<() => void>();\r\n /**\r\n * Phase B (presence-gated transfer picker). ONE long-lived watch, not a member of `#activeWatches`\r\n * — that set is per-transfer instances a live call spawns and forgets; this is a single background\r\n * connection tied to the softphone's own lifecycle, started and stopped by `#syncPresenceWatch`\r\n * rather than by any one call.\r\n */\r\n #presenceWatch: PresenceWatch | null = null;\r\n #presenceAgents: readonly PresenceEntry[] = [];\r\n\r\n /**\r\n * `tabLock` defaults to the real Web Locks implementation — not part of `SoftphoneConfig` (an\r\n * internal seam, not something an integrator tunes). Tests construct this class directly with a\r\n * fake for deterministic control; `createSoftphone()` never passes a third argument, so every\r\n * existing test that goes through it is unaffected (`multi-tab-leader-election.md` §3.8).\r\n */\r\n constructor(engine: CallEngine, config: SoftphoneConfig, tabLock: TabLock = webLocksTabLock()) {\r\n super();\r\n this.#engine = engine;\r\n this.#tabLock = tabLock;\r\n // Built here, not as a field initializer — it needs `this.#engine` (set the line above) and\r\n // `this.emit` (inherited from `Emitter`, not available until after `super()`), so it cannot be a\r\n // default value evaluated before either exists.\r\n this.audio = new BrowserAudioDevices(this.#engine, () =>\r\n this.emit(SoftphoneEvent.DeviceChange, undefined),\r\n );\r\n // ONE set of sources, built here so the widget can read them back instead of constructing a\r\n // second set from identical config (two name caches, two of every request).\r\n //\r\n // `apiBase` ALONE is enough to build these — `coventia` is for OVERRIDING something (your own\r\n // contacts function, an `organizationId`, a `baseUrl` that differs from the engine's), never for\r\n // switching the feature on. It used to be the switch: omitting `coventia` meant no Contactos tab\r\n // even with `apiBase` set for calling, which is the wrong default for OUR OWN backend's own\r\n // contacts/history/caller ID — a host would have to know to add an otherwise-empty `coventia: {}`\r\n // to get something this SDK should simply give them.\r\n //\r\n // One token for every request the sources make, instead of one per request — see\r\n // `cachedTokenSource`. Idempotent, so wrapping a source the engine already wrapped is free and\r\n // they share the same entry.\r\n this.sources =\r\n config.coventia || config.apiBase\r\n ? coventiaBackend(config.coventia ?? {}, cachedTokenSource(config.tokenSource), config.apiBase)\r\n : null;\r\n this.#resolveContact = config.resolveContact ?? this.sources?.resolveContact;\r\n this.#engine.onIncoming((engineCall) => this.#onIncoming(engineCall));\r\n this.#engine.onStateChange((state) => this.#setState(state));\r\n // Optional on the interface — an engine that never reports failures (the mock) simply has none.\r\n this.#engine.onError?.((error) => this.#onError(error));\r\n }\r\n\r\n get state(): SoftphoneState {\r\n return this.#state;\r\n }\r\n get lastError(): FatalSoftphoneError | null {\r\n return this.#lastError;\r\n }\r\n get isLeader(): boolean {\r\n return this.#isLeader;\r\n }\r\n get activeCall(): Call | null {\r\n return this.#activeCall;\r\n }\r\n get presence(): readonly PresenceEntry[] {\r\n return this.#presenceAgents;\r\n }\r\n get canPlayAudio(): boolean {\r\n // The autoplay-recovery machinery is Phase 2; the mock has no audio to block.\r\n return true;\r\n }\r\n async startAudio(): Promise<void> {\r\n /* autoplay recovery lands in Phase 2 */\r\n }\r\n\r\n async start(): Promise<void> {\r\n this.#assertUsable();\r\n if (this.#startedHere) return;\r\n if (liveInstance) {\r\n throw new Error(\r\n 'A Softphone is already running on this page. Construct one and share it; call destroy() before starting another. (SPEC §5.3 / error engine/duplicate-instance)',\r\n );\r\n }\r\n liveInstance = true;\r\n this.#startedHere = true;\r\n // Set BEFORE acquiring leadership, not after: `'starting'` means \"start() was called and is in\r\n // progress\", which includes waiting behind another tab as a real sub-phase of starting up — not\r\n // only the part after leadership is granted. This is what lets a host tell \"never started\" apart\r\n // from \"started, currently a follower\" (both otherwise look identical: `isLeader === false`,\r\n // and — had this stayed `'stopped'` here — `state === 'stopped'` too). The widget's `LeaderGate`\r\n // depends on this distinction (`multi-tab-leader-election.md` §3.4, §4).\r\n this.#setState('starting');\r\n\r\n // Resolves now for a single open tab (the common case — imperceptibly fast, nothing else holds\r\n // the lock), or later, whenever another tab releases it. See `multi-tab-leader-election.md` §3.4\r\n // for why this is NOT split into an immediate/deferred branch: nothing today awaits `start()`\r\n // closely enough to need one, and stop()/destroy() called while this is still pending (a\r\n // follower torn down before ever leading) simply leaves it permanently unresolved — see stop().\r\n await this.#tabLock.acquireLeadership();\r\n if (this.#destroyed) return; // torn down while waiting to lead — do not start the engine now\r\n this.#setLeader(true);\r\n await this.#engine.start();\r\n }\r\n\r\n async stop(): Promise<void> {\r\n if (!this.#startedHere) return;\r\n this.#tabLock.release();\r\n if (this.#isLeader) {\r\n await this.#engine.stop();\r\n if (this.#activeCall) await this.#activeCall.hangup().catch(() => {});\r\n }\r\n this.#setLeader(false);\r\n this.#releaseInstance();\r\n this.#setState('stopped');\r\n }\r\n\r\n async destroy(): Promise<void> {\r\n if (this.#destroyed) return;\r\n this.#destroyed = true;\r\n // Every timer this softphone owns beyond the engine's own — stopped first and unconditionally,\r\n // independent of #engine/#tabLock state below.\r\n for (const stopWatch of this.#activeWatches) stopWatch();\r\n this.#activeWatches.clear();\r\n this.#presenceWatch?.stop();\r\n this.#presenceWatch = null;\r\n await this.stop().catch(() => {});\r\n this.#setState('destroyed');\r\n this.clear();\r\n }\r\n\r\n async call(destination: string, options?: CallOptions): Promise<Call> {\r\n this.#assertUsable();\r\n if (this.#state !== 'online' && this.#state !== 'offline' && this.#state !== 'starting') {\r\n throw new Error(`Cannot place a call while softphone is '${this.#state}'.`);\r\n }\r\n if (this.#activeCall) {\r\n throw new Error('A call is already in progress. Hang up before placing another.');\r\n }\r\n const engineCall = await this.#engine.placeCall(destination, options);\r\n return this.#adopt(engineCall);\r\n }\r\n\r\n // ── internals ───────────────────────────────────────────────────────────────\r\n #onIncoming(engineCall: EngineCall): void {\r\n if (this.#activeCall) {\r\n // Busy: the engine is responsible for the 486. Nothing to surface.\r\n void engineCall.reject().catch(() => {});\r\n return;\r\n }\r\n const call = this.#adopt(engineCall);\r\n this.emit(SoftphoneEvent.IncomingCall, call);\r\n }\r\n\r\n #adopt(engineCall: EngineCall): CallImpl {\r\n const call = new CallImpl(\r\n engineCall,\r\n (event, error) => this.#reportListenerError('call', event, error),\r\n (message) => this.#reportInternalError(message),\r\n (target, callerNumber) => this.#onTransfer(target, callerNumber),\r\n );\r\n this.#activeCall = call;\r\n\r\n // Clear the slot when the call ends, so the next call can begin.\r\n const off = call.on(CallEvent.Ended, () => {\r\n off();\r\n if (this.#activeCall === call) {\r\n this.#activeCall = null;\r\n this.emit(SoftphoneEvent.ActiveCallChanged, { call: null });\r\n }\r\n });\r\n\r\n // Resolve the caller's name from the host's contacts and update the call in place. The provider's\r\n // SIP display name is the line label, not the caller (SPEC §4), so a resolved contact wins.\r\n if (this.#resolveContact) {\r\n void this.#resolveContact(engineCall.remoteNumber)\r\n .then((contact) => {\r\n if (contact && this.#activeCall === call) call.setRemoteContact(contact);\r\n })\r\n .catch(() => {});\r\n }\r\n\r\n // Fires for both inbound and outbound (both reach here) — what a UI's useActiveCall binds to.\r\n this.emit(SoftphoneEvent.ActiveCallChanged, { call });\r\n return call;\r\n }\r\n\r\n #setState(state: SoftphoneState): void {\r\n if (state === this.#state) return;\r\n const previous = this.#state;\r\n this.#state = state;\r\n /*\r\n * Reaching `online` is the only evidence we get that a fatal condition has passed, so it is what\r\n * clears it. Deliberately not a timer and not user input: neither is evidence of anything, and a\r\n * cleared error over a phone that still cannot call is worse than no error at all.\r\n *\r\n * For Zadarma `online` means the push socket is up, which is weaker than \"everything works\" — a\r\n * token could still be refused on the next key fetch. That self-corrects: the fetch fails, the\r\n * error is re-emitted, `lastError` is set again. An explanation that briefly lifts and returns\r\n * beats one that never lifts and needs a reload.\r\n */\r\n if (state === 'online') this.#lastError = null;\r\n this.emit(SoftphoneEvent.StateChanged, { state, previous });\r\n this.#syncPresenceWatch();\r\n }\r\n\r\n #setLeader(isLeader: boolean): void {\r\n if (isLeader === this.#isLeader) return;\r\n this.#isLeader = isLeader;\r\n this.emit(SoftphoneEvent.LeaderChanged, { isLeader });\r\n this.#syncPresenceWatch();\r\n }\r\n\r\n /**\r\n * Phase B (presence-gated transfer picker, `docs/specs/transfer-presence-picker.md` plan T6/T7).\r\n * The ONE place that decides whether the presence connection should be running right now — called\r\n * after every `#setState`/`#setLeader` transition rather than left to either alone, since either\r\n * one flipping can change the answer. No-ops entirely when no Coventia backend is configured\r\n * (`sources` is null) — same guard `#onTransfer` uses, and for the same reason: nothing to open.\r\n *\r\n * `presence-socket.ts`'s own `getIsLeader` is checked at start and before each reconnect attempt,\r\n * not on a poll tick the way `startTransferWatch`'s is — see that module's own doc comment for why.\r\n * This still passes `getIsLeader: () => this.#isLeader` for that narrower, defensive recheck; the\r\n * BROAD online/leader gate — whether to be connected AT ALL — is owned entirely here, the same\r\n * split `#onTransfer` already has with `startTransferWatch`.\r\n */\r\n #syncPresenceWatch(): void {\r\n const shouldRun = Boolean(this.sources) && this.#state === 'online' && this.#isLeader;\r\n if (shouldRun === (this.#presenceWatch !== null)) return;\r\n\r\n if (shouldRun) {\r\n this.#presenceWatch = startPresenceSocket({\r\n source: this.sources!.presence,\r\n getIsLeader: () => this.#isLeader,\r\n onAgentsChanged: (agents) => {\r\n this.#presenceAgents = agents;\r\n this.emit(SoftphoneEvent.PresenceChanged, { agents });\r\n },\r\n });\r\n } else {\r\n this.#presenceWatch?.stop();\r\n this.#presenceWatch = null;\r\n // A stale list from the connection that just closed must not keep being read as current —\r\n // mirrors `lastError`'s own \"cleared only on real evidence\" rule, just for the opposite fact:\r\n // losing the connection IS evidence there is nothing live left to show.\r\n this.#presenceAgents = [];\r\n this.emit(SoftphoneEvent.PresenceChanged, { agents: [] });\r\n }\r\n }\r\n\r\n /**\r\n * Phase A (transfer outcome relay, `docs/specs/transfer-outcome-relay.md`) — `CallImpl.transfer()`'s\r\n * internal seam calls this. No-ops entirely when no Coventia backend is configured (`sources` is\r\n * null): there is then nothing to poll, and behaviour is exactly what it was before this feature\r\n * existed (spec §5.4).\r\n */\r\n #onTransfer(target: string, callerNumber: string | null): void {\r\n if (!this.sources) return;\r\n const stopWatch = startTransferWatch({\r\n target,\r\n callerNumber,\r\n source: this.sources.transferOutcomes,\r\n getIsLeader: () => this.#isLeader,\r\n emit: (payload) => this.emit(SoftphoneEvent.TransferOutcome, payload),\r\n });\r\n this.#activeWatches.add(stopWatch);\r\n }\r\n\r\n /**\r\n * Re-emit an engine failure to the host, and remember it if it is fatal.\r\n *\r\n * Every error is emitted; only fatal ones are retained. A non-fatal error is a fact about one call\r\n * — the remote party was busy — and keeping it as softphone state would make a working phone look\r\n * broken to anything that renders `lastError`.\r\n */\r\n #onError(error: SoftphoneError): void {\r\n if (isFatal(error)) this.#lastError = error;\r\n this.emit(SoftphoneEvent.Error, error);\r\n }\r\n\r\n #releaseInstance(): void {\r\n if (this.#startedHere) {\r\n liveInstance = false;\r\n this.#startedHere = false;\r\n }\r\n }\r\n\r\n #assertUsable(): void {\r\n if (this.#destroyed) {\r\n throw new Error('This Softphone has been destroyed. Construct a new one. (engine/destroyed)');\r\n }\r\n }\r\n\r\n #reportListenerError(source: string, event: string | number | symbol, error: unknown): void {\r\n // A throwing host listener must never take the engine down. Surface it, don't swallow it.\r\n console.error(`[softphone] a ${source} '${String(event)}' listener threw:`, error);\r\n }\r\n\r\n #reportInternalError(message: string): void {\r\n console.error(`[softphone] ${message}`);\r\n }\r\n}\r\n\r\n/** Test-only reset of the page-singleton guard, so suites don't leak the flag between cases. */\r\nexport function __resetInstanceGuardForTests(): void {\r\n liveInstance = false;\r\n}\r\n","import type { CallEngine, EngineCall } from './engine.js';\r\nimport type { CallOptions, SoftphoneState, TokenSource } from './types.js';\r\nimport type { SoftphoneError } from './errors.js';\r\n\r\nexport interface DefaultProviderConfig {\r\n readonly apiBase: string;\r\n readonly tokenSource: TokenSource;\r\n readonly organizationId?: string | undefined;\r\n}\r\n\r\n/**\r\n * A subscription list that may not have a real target yet.\r\n *\r\n * `defaultZadarmaEngine` below has THREE of these (`onIncoming`, `onStateChange`, `onError`), all\r\n * needing the identical, slightly tricky behaviour: a listener registered before the real engine\r\n * exists must still receive events once it does, AND unsubscribing must work correctly regardless of\r\n * whether it happens before or after that engine shows up. Three independent copies of this is three\r\n * independent chances to get the \"unsubscribe after the real engine attached\" case wrong — silently,\r\n * since nothing in this file's one current caller (`SoftphoneImpl`) ever unsubscribes these three, so\r\n * a bug here would only bite a future caller. One small class, three call sites, no reason to write\r\n * (and maintain) the replay/unsubscribe logic three times.\r\n */\r\nclass LazyRelay<L extends (...args: never[]) => void> {\r\n readonly #listeners = new Set<L>();\r\n readonly #realOff = new Map<L, () => void>();\r\n #target: { on(listener: L): () => void } | null = null;\r\n\r\n subscribe(listener: L): () => void {\r\n this.#listeners.add(listener);\r\n if (this.#target) this.#realOff.set(listener, this.#target.on(listener));\r\n return () => {\r\n this.#listeners.delete(listener);\r\n this.#realOff.get(listener)?.();\r\n this.#realOff.delete(listener);\r\n };\r\n }\r\n\r\n /** Called once the real engine exists — replays every listener registered before now onto it. */\r\n attach(target: { on(listener: L): () => void }): void {\r\n this.#target = target;\r\n for (const listener of this.#listeners) this.#realOff.set(listener, target.on(listener));\r\n }\r\n}\r\n\r\n/**\r\n * The default call engine: Zadarma, loaded lazily.\r\n *\r\n * Returns a `CallEngine` SYNCHRONOUSLY — `createSoftphone` must stay synchronous and SSR-safe even\r\n * when `provider` is omitted — and defers the real work to the first genuine use (`start()` or\r\n * `placeCall()`). Only then does it dynamically `import('../transports/zadarma-engine.js')`, which is\r\n * what actually reaches jssip and browser globals. Exactly the trick `coventiaBackend` (`backend.ts`)\r\n * already uses for contacts/history; applied here to the engine instead.\r\n *\r\n * The import specifier is a STRING LITERAL, not a variable — `check-imports.mjs` enforces this across\r\n * the whole build, after a variable specifier here once shipped a bare specifier no bundler could\r\n * resolve, which typechecked, passed 211 tests (all against fakes), and broke only in a real browser.\r\n */\r\nexport function defaultZadarmaEngine(config: DefaultProviderConfig): CallEngine {\r\n let real: CallEngine | null = null;\r\n let pending: Promise<CallEngine> | null = null;\r\n\r\n const incoming = new LazyRelay<(call: EngineCall) => void>();\r\n const stateChange = new LazyRelay<(state: SoftphoneState) => void>();\r\n const error = new LazyRelay<(error: SoftphoneError) => void>();\r\n\r\n function load(): Promise<CallEngine> {\r\n pending ??= import('../transports/zadarma-engine.js')\r\n .then(({ createZadarmaEngine }) => {\r\n const engine = createZadarmaEngine(config);\r\n incoming.attach({ on: (l) => engine.onIncoming(l) });\r\n stateChange.attach({ on: (l) => engine.onStateChange(l) });\r\n error.attach({ on: (l) => engine.onError?.(l) ?? (() => {}) });\r\n real = engine;\r\n return engine;\r\n })\r\n .catch((cause: unknown) => {\r\n // A retry (the widget's `Reintentar`: `stop()` then `start()` again) must be a genuine second\r\n // attempt, not a replay of the same cached failure — clearing `pending` is what makes that\r\n // true. Without it, one failed chunk load would fail every future retry forever.\r\n pending = null;\r\n throw cause;\r\n });\r\n return pending;\r\n }\r\n\r\n return {\r\n get state(): SoftphoneState {\r\n return real?.state ?? 'stopped';\r\n },\r\n async start(): Promise<void> {\r\n await (await load()).start();\r\n },\r\n async stop(): Promise<void> {\r\n // Never successfully started (or a failed load left `real` unset) — nothing to stop, and\r\n // calling `load()` here would wrongly turn a no-op into a first attempt.\r\n if (real) await real.stop();\r\n },\r\n async placeCall(destination: string, options?: CallOptions): Promise<EngineCall> {\r\n return (await load()).placeCall(destination, options);\r\n },\r\n onIncoming: (listener) => incoming.subscribe(listener),\r\n onStateChange: (listener) => stateChange.subscribe(listener),\r\n onError: (listener) => error.subscribe(listener),\r\n };\r\n}\r\n","import { SoftphoneImpl } from './softphone-impl.js';\r\nimport { defaultZadarmaEngine } from './default-provider.js';\r\nimport type { CallEngine } from './engine.js';\r\nimport type { Softphone } from './softphone.js';\r\nimport type { SoftphoneConfig } from './types.js';\r\n\r\n/**\r\n * Construct a softphone.\r\n *\r\n * A factory, not `new Softphone()`, for two reasons: it resolves the underlying call engine from\r\n * config without exposing that machinery, and it returns the `Softphone` INTERFACE, so the concrete\r\n * class stays an implementation detail free to change without a major version.\r\n *\r\n * @example\r\n * const phone = createSoftphone({\r\n * tokenSource: TokenSource.endpoint('/voice/token'),\r\n * resolveContact: (n) => api.lookup(n),\r\n * });\r\n * await phone.start(); // from a user gesture — see Softphone.start()\r\n */\r\nexport function createSoftphone(config: SoftphoneConfig): Softphone {\r\n return new SoftphoneImpl(resolveEngine(config), config);\r\n}\r\n\r\n/**\r\n * Pick the call engine.\r\n *\r\n * Three outcomes, in order:\r\n * 1. A valid `config.provider` — used as-is. `apiBase` is never even looked at: an explicit engine\r\n * always wins, so a host who wants a `MockProvider` or their own `organizationId` gets exactly\r\n * that, with no default quietly built alongside it.\r\n * 2. `provider` was SET but is not a valid engine — thrown on immediately, distinctly from \"none\r\n * configured\" below. Silently falling through to the default here would turn a typo'd config\r\n * into a confusing \"why is this calling Zadarma, I passed something else\" bug report.\r\n * 3. `provider` omitted, `apiBase` present — `defaultZadarmaEngine` (SPEC §5.4). It resolves\r\n * synchronously and touches nothing browser-specific until `start()`/`placeCall()` actually run,\r\n * so this stays as SSR-safe as the explicit-provider path.\r\n *\r\n * Neither present: a plain, explicit throw beats a confusing failure deep inside `start()`.\r\n */\r\nfunction resolveEngine(config: SoftphoneConfig): CallEngine {\r\n if (config.provider !== undefined) {\r\n if (isCallEngine(config.provider)) return config.provider;\r\n throw new Error(\r\n '`config.provider` was set but is not a valid CallEngine — it needs at least `placeCall` and ' +\r\n '`onIncoming`. Pass `createZadarmaEngine(...)` from \"@coventia/softphone/zadarma\", a ' +\r\n '`MockProvider` from \"@coventia/softphone/testing\", or omit it entirely to use the default.',\r\n );\r\n }\r\n\r\n if (config.apiBase) {\r\n return defaultZadarmaEngine({\r\n apiBase: config.apiBase,\r\n tokenSource: config.tokenSource,\r\n organizationId: config.coventia?.organizationId,\r\n });\r\n }\r\n\r\n throw new Error(\r\n 'No call engine configured. Pass `config.provider` explicitly — `createZadarmaEngine({ apiBase, ' +\r\n 'tokenSource })` from \"@coventia/softphone/zadarma\", or a `MockProvider` from ' +\r\n '\"@coventia/softphone/testing\" — or set `config.apiBase` so a default Zadarma engine can be built.',\r\n );\r\n}\r\n\r\nfunction isCallEngine(value: unknown): value is CallEngine {\r\n return (\r\n typeof value === 'object' &&\r\n value !== null &&\r\n typeof (value as CallEngine).placeCall === 'function' &&\r\n typeof (value as CallEngine).onIncoming === 'function'\r\n );\r\n}\r\n","/**\r\n * @coventia/softphone — the framework-free engine.\r\n *\r\n * Importable from vanilla JS, Vue, Svelte, a PHP-rendered page, and a Node/SSR process. Exports NO\r\n * React and touches NO browser global at module scope (SPEC §10.1, §10.2). Those two rules — not the\r\n * authoring language, not the package layout — are what make this usable anywhere.\r\n *\r\n * THIS FILE IS THE PUBLIC API. Anything not exported here is free to change without a major version;\r\n * anything exported here is not. Adding to it is cheap and removing from it is expensive, so add\r\n * deliberately.\r\n *\r\n * Notably NOT exported, and deliberately:\r\n * - `SipProvider` and everything under transports/ — the provider seam is internal (SPEC §5.4).\r\n * Exporting it would freeze the shape of a Zadarma migration we have not designed yet.\r\n * - `MockProvider` — lives at @coventia/softphone/testing, so it never lands in a production bundle.\r\n */\r\n\r\n// ── Engine ────────────────────────────────────────────────────────────────────\r\nexport { createSoftphone } from './core/create.js';\r\nexport type { Softphone, AudioDevices } from './core/softphone.js';\r\n\r\n// ── Calls ─────────────────────────────────────────────────────────────────────\r\nexport type { Call, CallState, HangupReason, HoldOriginator } from './core/call.js';\r\nexport { CALL_TRANSITIONS, INITIAL_STATES, canTransition, isTerminal } from './core/call.js';\r\n\r\n// ── Events ────────────────────────────────────────────────────────────────────\r\nexport { SoftphoneEvent, CallEvent } from './core/events.js';\r\nexport type { SoftphoneEventMap, CallEventMap, TypedEmitter } from './core/events.js';\r\n\r\n// ── Errors ────────────────────────────────────────────────────────────────────\r\n// `isFatal` is public because a host has to branch on it — `err.fatal` narrows structurally, but a\r\n// guard is friendlier from JavaScript. `createError` stays internal: the SDK raises these, hosts read\r\n// them, and a host minting our error codes would be claiming a failure we never observed.\r\nexport { ERROR_CATALOGUE, isFatal } from './core/errors.js';\r\nexport type {\r\n SoftphoneError,\r\n SoftphoneErrorCode,\r\n FatalSoftphoneError,\r\n FatalErrorCode,\r\n NonFatalSoftphoneError,\r\n NonFatalErrorCode,\r\n} from './core/errors.js';\r\n\r\n// ── Config & types ────────────────────────────────────────────────────────────\r\nexport type {\r\n SoftphoneConfig,\r\n SoftphoneState,\r\n CallDirection,\r\n CallOptions,\r\n Contact,\r\n RemoteParty,\r\n LogLevel,\r\n TokenSource,\r\n TokenEndpointOptions,\r\n VoiceToken,\r\n} from './core/types.js';\r\n\r\n// ── Capability probes ─────────────────────────────────────────────────────────\r\nexport { isBrowser, isWebRtcSupported } from './core/env.js';\r\n\r\nexport const VERSION = '0.2.0';\r\n"]}
1
+ {"version":3,"sources":["../src/core/emitter.ts","../src/core/call.ts","../src/core/call-impl.ts","../src/core/backend.ts","../src/core/tabs.ts","../src/core/transfer-watch.ts","../src/core/presence-socket.ts","../src/core/softphone-impl.ts","../src/core/default-provider.ts","../src/core/create.ts","../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { TypedEmitter } from './events.js';\r\n\r\n/**\r\n * A tiny typed event emitter. Framework-free — no Node `events`, no browser globals (SPEC §10.2).\r\n *\r\n * `on()` / `once()` return an unsubscribe function (SPEC §5.3 / events.ts): cleanup is impossible to\r\n * get wrong in a React effect, and there is no opaque handle to retain (Vonage's mistake).\r\n *\r\n * A listener that throws must not stop the other listeners or corrupt the emitter — a bug in the\r\n * host's UI code should never take the call engine down mid-call. Throws are routed to `onError` so\r\n * the engine can log them without swallowing them silently.\r\n */\r\n// Unconstrained, matching TypedEmitter: the event maps are keyed by enum members (CallEvent,\r\n// SoftphoneEvent), which are closed object types, not string index signatures.\r\nexport class Emitter<M> implements TypedEmitter<M> {\r\n // A copy of the listener set is iterated on emit, so a listener that unsubscribes (or subscribes)\r\n // during dispatch does not mutate the set mid-iteration.\r\n readonly #listeners = new Map<keyof M, Set<(payload: never) => void>>();\r\n\r\n constructor(private readonly onListenerError?: (event: keyof M, error: unknown) => void) {}\r\n\r\n on<E extends keyof M>(event: E, listener: (payload: M[E]) => void): () => void {\r\n let set = this.#listeners.get(event);\r\n if (!set) {\r\n set = new Set();\r\n this.#listeners.set(event, set);\r\n }\r\n set.add(listener as (payload: never) => void);\r\n return () => this.off(event, listener);\r\n }\r\n\r\n once<E extends keyof M>(event: E, listener: (payload: M[E]) => void): () => void {\r\n const wrapped = (payload: M[E]): void => {\r\n off();\r\n listener(payload);\r\n };\r\n const off = this.on(event, wrapped);\r\n return off;\r\n }\r\n\r\n off<E extends keyof M>(event: E, listener: (payload: M[E]) => void): void {\r\n const set = this.#listeners.get(event);\r\n if (!set) return;\r\n set.delete(listener as (payload: never) => void);\r\n if (set.size === 0) this.#listeners.delete(event);\r\n }\r\n\r\n emit<E extends keyof M>(event: E, payload: M[E]): void {\r\n const set = this.#listeners.get(event);\r\n if (!set) return;\r\n for (const listener of [...set]) {\r\n try {\r\n (listener as (p: M[E]) => void)(payload);\r\n } catch (error) {\r\n // A throwing listener must not abort the dispatch loop or bubble into the engine.\r\n this.onListenerError?.(event, error);\r\n }\r\n }\r\n }\r\n\r\n /** Drop every listener. Called on teardown so a destroyed object leaks nothing. */\r\n clear(): void {\r\n this.#listeners.clear();\r\n }\r\n}\r\n","import type { CallEventMap, TypedEmitter } from './events.js';\r\nimport type { CallDirection, RemoteParty } from './types.js';\r\n\r\n/**\r\n * THE call state machine. Singular.\r\n *\r\n * Today there are three competing representations (SPEC §5.3):\r\n * - `callStateRef` — a ref, read synchronously in guards to dodge stale closures\r\n * - `status` — a useState that can lag a render behind it\r\n * - `consultStatus` — the attended-transfer leg, tracked separately\r\n * They can disagree. Collapsing them into one is the single biggest correctness win in this\r\n * refactor, and the consult leg simply becomes its own Call.\r\n *\r\n *\r\n * INBOUND OUTBOUND\r\n * ─────── ────────\r\n * ringing ──answer()──► answering dialing ──180/183──► early\r\n * │ │ │ │\r\n * │ ▼ └────────┬───────────┘\r\n * │ reject() ┌───────┐ │ 200 OK\r\n * └──────────────┐ │ ▼ ▼\r\n * │ │ active ◄─────────────────────\r\n * │ │ ▲ │ ▲ │\r\n * │ │ │ │ │ └──hold()/remote──► held ──unhold()──┐\r\n * │ │ │ │ └───────────────────────────────────────-┘\r\n * │ │ │ └──ICE fail──► reconnecting ──recovered──┐\r\n * │ │ └────────────────────────────────────────────┘\r\n * ▼ ▼ │\r\n * ┌──────────────▼────┐\r\n * │ ended │ TERMINAL. Reachable from every state.\r\n * └────────────────────┘\r\n *\r\n * NOT modelled here, deliberately:\r\n *\r\n * - **`rejecting`.** Today's engine has one, purely to swallow Zadarma's `return_timeout` retries\r\n * for 35 s after a decline (SPEC §3.4). That is a PROVIDER quirk — it belongs in\r\n * `providers/zadarma.ts` as `quirks.rejectGuardMs`, not in the shared state machine. A rejected\r\n * call is simply `ended`. Our own SIP infrastructure would not need it at all (SPEC §2.3).\r\n *\r\n * - **A pre-INVITE state.** A Call exists only once an INVITE has landed. The \"user answered\r\n * before the INVITE arrived\" case (SPEC §3.1) is an *intent* held on `Softphone`, above the\r\n * call — because there is no call yet to hold it. Same for a pre-INVITE reject, which registers\r\n * a UA specifically so it has something to send the 486 from.\r\n */\r\nexport type CallState =\r\n /** Inbound, INVITE received, not yet answered. */\r\n | 'ringing'\r\n /** Outbound, INVITE sent, no provisional response yet. */\r\n | 'dialing'\r\n /** Outbound, remote is ringing (180/183). May carry early media. */\r\n | 'early'\r\n /** Local answer in flight — 200 OK not yet confirmed. */\r\n | 'answering'\r\n /** Media flowing. */\r\n | 'active'\r\n /** Real SIP hold (`a=sendonly`), by either party. */\r\n | 'held'\r\n /** Media lost, ICE restart in flight. The call is NOT over. */\r\n | 'reconnecting'\r\n /** Terminal. */\r\n | 'ended';\r\n\r\nexport type HoldOriginator = 'local' | 'remote';\r\n\r\nexport type HangupReason =\r\n | 'local'\r\n | 'remote'\r\n | 'rejected'\r\n | 'busy'\r\n | 'no-answer'\r\n | 'transferred'\r\n | 'failed';\r\n\r\n/**\r\n * The legal transitions, as data.\r\n *\r\n * A table rather than scattered `if`s: it is testable in isolation, it is the thing a reviewer can\r\n * actually check against the diagram above, and an illegal transition becomes a caught bug instead\r\n * of a silently wrong UI.\r\n */\r\nexport const CALL_TRANSITIONS = {\r\n ringing: ['answering', 'ended'],\r\n dialing: ['early', 'active', 'ended'],\r\n early: ['active', 'ended'],\r\n answering: ['active', 'ended'],\r\n active: ['held', 'reconnecting', 'ended'],\r\n held: ['active', 'reconnecting', 'ended'],\r\n reconnecting: ['active', 'held', 'ended'],\r\n ended: [],\r\n} as const satisfies Readonly<Record<CallState, readonly CallState[]>>;\r\n\r\n/** The states a call can start in. Everything else is reached by transition. */\r\nexport const INITIAL_STATES = {\r\n inbound: 'ringing',\r\n outbound: 'dialing',\r\n} as const satisfies Readonly<Record<CallDirection, CallState>>;\r\n\r\nexport function canTransition(from: CallState, to: CallState): boolean {\r\n return (CALL_TRANSITIONS[from] as readonly CallState[]).includes(to);\r\n}\r\n\r\nexport function isTerminal(state: CallState): boolean {\r\n return CALL_TRANSITIONS[state].length === 0;\r\n}\r\n\r\n/**\r\n * Whether a call in `state` means \"this agent is on a call\" for PRESENCE (presence-client-reporting.md §3.1) —\r\n * what the softphone tells the backend about itself. Mirrors what the webhooks mark: the dialer from the moment\r\n * it starts ringing out (`dialing`, `early`), the answerer from the answer (`answering`, `active`), and a call\r\n * stays on through `held` and `reconnecting` (the call is NOT over). An inbound call that is still `ringing`\r\n * is not: an unanswered ring never marked anyone. Internal — not part of the public surface.\r\n */\r\nexport function isOnCallState(state: CallState): boolean {\r\n return state !== 'ringing' && !isTerminal(state);\r\n}\r\n\r\n/**\r\n * A single call. Exists only while there is one — there is no `idle`.\r\n *\r\n * The attended-transfer consult leg is *also* a Call. That is what lets one state machine cover\r\n * both, instead of today's separate `consultStatus`.\r\n *\r\n * Implementation lands in S2 (against MockProvider) and S3–S4 (against JsSIP/Zadarma).\r\n */\r\nexport interface Call extends TypedEmitter<CallEventMap> {\r\n readonly id: string;\r\n readonly direction: CallDirection;\r\n readonly state: CallState;\r\n readonly remote: RemoteParty;\r\n /** Wall-clock start of media. Null until `active`. */\r\n readonly answeredAt: Date | null;\r\n\r\n readonly isMuted: boolean;\r\n readonly isOnHold: boolean;\r\n\r\n /**\r\n * The remote party's audio stream, once media is flowing (null before). Attach it to an\r\n * `<audio autoplay>` element to hear the call. Also delivered via `CallEvent.RemoteStream`.\r\n */\r\n readonly remoteStream: MediaStream | null;\r\n\r\n /** Only legal from `ringing`. Throws `call/not-answerable` otherwise. */\r\n answer(): Promise<void>;\r\n /** Only legal from `ringing`. Sends 486 Busy Here. */\r\n reject(): Promise<void>;\r\n /** Legal from any non-terminal state. Idempotent. */\r\n hangup(): Promise<void>;\r\n\r\n mute(muted: boolean): void;\r\n\r\n /**\r\n * Real SIP hold — a re-INVITE with `a=sendonly`, so the PBX can play hold music.\r\n *\r\n * Today this is FAKED by muting (SPEC §5.6): the held caller hears dead silence and cannot tell\r\n * hold from a dropped call. JsSIP implements the real thing natively, so this is a handful of\r\n * lines rather than the SIP work it looks like.\r\n */\r\n hold(): Promise<void>;\r\n unhold(): Promise<void>;\r\n\r\n sendDigits(digits: string): void;\r\n\r\n /**\r\n * Blind transfer (SIP REFER). Ends this call — but **whether the transfer itself succeeded or\r\n * failed is unknowable from here**: against Zadarma, a REFER's outcome is not reported back to the\r\n * browser at all (confirmed on real calls — `docs/specs/transfer-availability.md` §2), so a REFER to\r\n * an unreachable target ends this call exactly the same way a successful one does. Prefer\r\n * `startConsult`/`completeTransfer` (below) when the caller must not be put at risk — that path\r\n * correctly reports busy/no-answer/failed because the consult leg is an ordinary outbound call, not\r\n * a REFER.\r\n */\r\n transfer(target: string): Promise<void>;\r\n\r\n /**\r\n * Attended transfer, part 1: call the target while holding this call.\r\n *\r\n * Returns the consult leg — itself a Call, with its own state machine and events. Unlike blind\r\n * `transfer()`, a consult leg that goes unanswered reports a real reason (busy/no-answer/rejected/\r\n * failed) and never puts this call at risk — see `docs/specs/transfer-availability.md` §5.\r\n */\r\n startConsult(target: string): Promise<Call>;\r\n /**\r\n * Attended transfer, part 2: bridge the two, ending both. (REFER … Replaces)\r\n *\r\n * **Confirmed non-functional against Zadarma, on real calls, three times\r\n * (`docs/specs/transfer-availability.md` §12.3).** Zadarma accepts the REFER…Replaces but never\r\n * actually bridges the two legs — the consult leg is silently orphaned and the caller is lost, with\r\n * no error thrown here to signal it. The pattern that DOES work (the same one `/ui`'s own\r\n * `ConsultScreen` uses): hang up the consult leg, then call `transfer(target)` on THIS call, to the\r\n * same already-confirmed-answering target — a blind transfer to a target already known to be there\r\n * works, even though `completeTransfer()` itself does not. Left in the type for hosts against a\r\n * different (non-Zadarma) SIP provider where REFER…Replaces may work as specified; do not reach for\r\n * it against Zadarma.\r\n */\r\n completeTransfer(): Promise<void>;\r\n /** Abandon the consult and resume this call. */\r\n cancelConsult(): Promise<void>;\r\n}\r\n","import { Emitter } from './emitter.js';\r\nimport { CallEvent, type CallEventMap } from './events.js';\r\nimport {\r\n INITIAL_STATES,\r\n canTransition,\r\n isTerminal,\r\n type Call,\r\n type CallState,\r\n type HangupReason,\r\n} from './call.js';\r\nimport type { EngineCall, EngineCallEvent } from './engine.js';\r\nimport type { Contact, RemoteParty } from './types.js';\r\n\r\n/**\r\n * The concrete call. Owns the state machine (SPEC §5.3) and the public event stream.\r\n *\r\n * It never talks to SIP or the provider — it talks to an `EngineCall`, translating that engine's\r\n * RAW signals into public state transitions in one place. The mock's scripted signals and the real\r\n * engine's provider-confirmed ones travel the identical path, which is what keeps the playground\r\n * honest against production.\r\n */\r\nexport class CallImpl extends Emitter<CallEventMap> implements Call {\r\n #state: CallState;\r\n #remote: RemoteParty;\r\n #answeredAt: Date | null = null;\r\n #muted = false;\r\n #onHold = false;\r\n #remoteStream: MediaStream | null = null;\r\n #consultEngineCall: EngineCall | null = null;\r\n #consultCall: Call | null = null;\r\n readonly #unsubscribe: () => void;\r\n\r\n readonly #onListenerError: ((event: keyof CallEventMap, error: unknown) => void) | undefined;\r\n\r\n constructor(\r\n private readonly engineCall: EngineCall,\r\n onListenerError?: (event: keyof CallEventMap, error: unknown) => void,\r\n private readonly onInternalError?: (message: string) => void,\r\n /**\r\n * Fired after `transfer()` resolves, with the target and the ORIGINAL caller's number — an\r\n * internal seam for the softphone's transfer-outcome watch (Phase A,\r\n * `docs/specs/transfer-outcome-relay.md`), never part of the public `Call` interface. Not\r\n * consulted for `startConsult`/`completeTransfer`: those legs already have their own real\r\n * `HangupReason`s (SPEC §5.6) and need no external polling to know what happened to them.\r\n */\r\n private readonly onTransfer?: (target: string, callerNumber: string | null) => void,\r\n ) {\r\n super(onListenerError);\r\n this.#onListenerError = onListenerError;\r\n this.#state = INITIAL_STATES[engineCall.direction];\r\n this.#remote = {\r\n number: engineCall.remoteNumber,\r\n displayName: engineCall.remoteNumber,\r\n contact: null,\r\n };\r\n this.#unsubscribe = engineCall.onEvent((event) => this.#onEngineEvent(event));\r\n }\r\n\r\n get id(): string {\r\n return this.engineCall.id;\r\n }\r\n get direction() {\r\n return this.engineCall.direction;\r\n }\r\n get state(): CallState {\r\n return this.#state;\r\n }\r\n get remote(): RemoteParty {\r\n return this.#remote;\r\n }\r\n get answeredAt(): Date | null {\r\n return this.#answeredAt;\r\n }\r\n get isMuted(): boolean {\r\n return this.#muted;\r\n }\r\n get isOnHold(): boolean {\r\n return this.#onHold;\r\n }\r\n get remoteStream(): MediaStream | null {\r\n return this.#remoteStream;\r\n }\r\n\r\n // ── Raw engine signals → state machine ──────────────────────────────────────\r\n #onEngineEvent(event: EngineCallEvent): void {\r\n switch (event.type) {\r\n case 'progress':\r\n this.#transition('early');\r\n break;\r\n case 'accepted':\r\n if (this.#answeredAt === null) this.#answeredAt = this.#now();\r\n this.#transition('active');\r\n this.emit(CallEvent.Accepted, undefined);\r\n break;\r\n case 'held':\r\n this.#onHold = true;\r\n if (this.#transition('held')) this.emit(CallEvent.Held, { originator: event.originator });\r\n break;\r\n case 'unheld':\r\n this.#onHold = false;\r\n if (this.#transition('active')) this.emit(CallEvent.Unheld, { originator: event.originator });\r\n break;\r\n case 'reconnecting':\r\n if (this.#transition('reconnecting')) this.emit(CallEvent.Reconnecting, undefined);\r\n break;\r\n case 'reconnected':\r\n if (this.#transition(this.#onHold ? 'held' : 'active'))\r\n this.emit(CallEvent.Reconnected, undefined);\r\n break;\r\n case 'ended':\r\n this.#end(event.reason);\r\n break;\r\n case 'stream':\r\n // Expose the remote audio so the host can play it. Core must not create an <audio> element\r\n // itself (SPEC §10.2) — it hands the stream out and the UI/host attaches it.\r\n this.#remoteStream = event.stream;\r\n this.emit(CallEvent.RemoteStream, { stream: event.stream });\r\n break;\r\n }\r\n }\r\n\r\n // ── Commands ────────────────────────────────────────────────────────────────\r\n async answer(): Promise<void> {\r\n if (this.#state !== 'ringing') {\r\n throw this.#stateError('answer', 'call/not-answerable');\r\n }\r\n // Move to `answering` BEFORE awaiting the engine, so a fast provider \"answered\" signal cannot\r\n // race past the ringing state (SPEC §3.1). The engine confirms with an `accepted` event, which\r\n // is what actually reaches `active`.\r\n this.#transition('answering');\r\n await this.engineCall.answer();\r\n }\r\n\r\n async reject(): Promise<void> {\r\n if (this.#state !== 'ringing') {\r\n throw this.#stateError('reject', 'call/not-answerable');\r\n }\r\n await this.engineCall.reject();\r\n // The engine emits `ended` with reason 'rejected' — no local transition here, so there is one\r\n // path to `ended`.\r\n }\r\n\r\n async hangup(): Promise<void> {\r\n if (isTerminal(this.#state)) return; // idempotent\r\n // A live consult leg (SPEC §5.6) is a REAL second call to the transfer target — hanging up on the\r\n // caller without also ending it would leave that extension ringing forever, orphaned, with nothing\r\n // left holding a reference to it once this call reaches `ended`. Ending it HERE (rather than only\r\n // relying on `#end()`'s own safety net below) fires both hangups in parallel instead of waiting for\r\n // this call's own round trip to complete first — `#end()` still needs its copy of this for every\r\n // OTHER way the main call can end (the caller hanging up first, an engine failure, anything that\r\n // never calls this method at all).\r\n const consult = this.#consultCall;\r\n this.#consultCall = null;\r\n this.#consultEngineCall = null;\r\n await Promise.all([consult?.hangup() ?? Promise.resolve(), this.engineCall.hangup()]);\r\n }\r\n\r\n mute(muted: boolean): void {\r\n if (this.#muted === muted) return;\r\n this.#muted = muted;\r\n this.engineCall.mute(muted);\r\n this.emit(CallEvent.Muted, { muted });\r\n }\r\n\r\n async hold(): Promise<void> {\r\n if (this.#onHold || (this.#state !== 'active' && this.#state !== 'reconnecting')) return;\r\n await this.engineCall.hold();\r\n // The engine confirms with a `held` event (originator: 'local').\r\n }\r\n\r\n async unhold(): Promise<void> {\r\n if (!this.#onHold) return;\r\n await this.engineCall.unhold();\r\n }\r\n\r\n sendDigits(digits: string): void {\r\n this.engineCall.sendDigits(digits);\r\n this.emit(CallEvent.DigitsSent, { digits });\r\n }\r\n\r\n async transfer(target: string): Promise<void> {\r\n await this.engineCall.transfer(target);\r\n // Blind transfer ends this call; the engine emits `ended` with reason 'transferred'.\r\n this.onTransfer?.(target, this.#remote.number.trim() || null);\r\n }\r\n\r\n async startConsult(target: string): Promise<Call> {\r\n // Put the caller on REAL hold (a=sendonly → the PBX plays hold music) before dialling the\r\n // consult target — otherwise they hear the agent's consult conversation. The legacy softphone\r\n // FAKED this by muting, so the caller heard dead silence (SPEC §5.6); real hold is the fix.\r\n await this.hold();\r\n\r\n const consultEngineCall = await this.engineCall.startConsult(target);\r\n // The consult leg is a first-class Call with its own state machine — this is what replaces\r\n // today's separate `consultStatus` (SPEC §5.3). We keep the underlying engine handle so\r\n // completeTransfer can bridge the two legs (REFER…Replaces).\r\n this.#consultEngineCall = consultEngineCall;\r\n const consult = new CallImpl(consultEngineCall, this.#onListenerError, this.onInternalError);\r\n this.#consultCall = consult;\r\n return consult;\r\n }\r\n\r\n async completeTransfer(): Promise<void> {\r\n if (!this.#consultEngineCall) throw this.#stateError('completeTransfer', 'call/unsupported');\r\n // The bridge (REFER…Replaces) ends both legs; the caller leaves hold by being connected to the\r\n // target, so there is nothing to unhold — the engine emits `ended` on each.\r\n await this.engineCall.completeTransfer(this.#consultEngineCall);\r\n }\r\n\r\n async cancelConsult(): Promise<void> {\r\n if (!this.#consultCall) return;\r\n await this.#consultCall.hangup();\r\n this.#consultCall = null;\r\n this.#consultEngineCall = null;\r\n // Take the caller off hold — the agent is coming back to them.\r\n await this.unhold();\r\n }\r\n\r\n // ── Internals ───────────────────────────────────────────────────────────────\r\n #transition(to: CallState): boolean {\r\n const from = this.#state;\r\n if (from === to) return false;\r\n if (isTerminal(from)) return false; // never leave `ended`\r\n if (!canTransition(from, to)) {\r\n this.onInternalError?.(`illegal call transition ${from} → ${to} (SPEC §5.3)`);\r\n return false;\r\n }\r\n this.#state = to;\r\n this.emit(CallEvent.StateChanged, { state: to, previous: from });\r\n return true;\r\n }\r\n\r\n #end(reason: HangupReason): void {\r\n if (isTerminal(this.#state)) return;\r\n // Safety net for every path that ends THIS call without going through `hangup()` above — the\r\n // caller hanging up on their own while a consult is in progress, an engine failure, anything the\r\n // provider reports as `ended`. Found live (`docs/specs/transfer-availability.md`): without this,\r\n // only an agent-initiated hangup ever cleaned up the consult leg, so the caller hanging up first\r\n // left the transfer target on a real, ringing-or-answered call with nobody left holding a\r\n // reference to it — the exact \"ghost call\" this method's sibling was written to prevent, just\r\n // reachable from a different direction. `hangup()`'s own copy still runs first when it applies —\r\n // by the time its `engineCall.hangup()` round-trips back here, `#consultCall` is already `null`,\r\n // so this is a no-op in that case, not a double-hangup.\r\n if (this.#consultCall) {\r\n const consult = this.#consultCall;\r\n this.#consultCall = null;\r\n this.#consultEngineCall = null;\r\n void consult.hangup();\r\n }\r\n const from = this.#state;\r\n this.#state = 'ended';\r\n this.emit(CallEvent.StateChanged, { state: 'ended', previous: from });\r\n this.emit(CallEvent.Ended, { reason });\r\n this.#unsubscribe();\r\n this.clear();\r\n }\r\n\r\n #stateError(op: string, _code: 'call/not-answerable' | 'call/unsupported'): Error {\r\n // A real SoftphoneError is minted in S2's errors implementation; a plain Error keeps the type\r\n // surface honest until then.\r\n return new Error(`${op}() is not valid while call is '${this.#state}'`);\r\n }\r\n\r\n #now(): Date {\r\n // Isolated so tests can stub it, and so we never reach for a bare `new Date()` in logic.\r\n return new Date();\r\n }\r\n\r\n /** @internal — the Softphone updates caller identity once `resolveContact` resolves. */\r\n setRemoteContact(contact: Contact): void {\r\n this.#remote = {\r\n number: this.#remote.number,\r\n displayName: contact.name ?? this.#remote.number,\r\n contact,\r\n };\r\n this.emit(CallEvent.RemotePartyUpdated, undefined);\r\n }\r\n}\r\n","import type {\r\n AgentsDirectory,\r\n Contact,\r\n ContactsSource,\r\n CoventiaBackendConfig,\r\n HistorySource,\r\n PresenceSource,\r\n RingtoneSource,\r\n TokenSource,\r\n TransferOutcomesPage,\r\n} from './types.js';\r\n\r\n/** Contacts, history, caller ID, the agents directory, transfer outcomes, presence and ringtone\r\n * settings, all from one config. */\r\nexport interface BackendSources {\r\n readonly contacts: ContactsSource;\r\n readonly history: HistorySource;\r\n readonly resolveContact: (number: string) => Promise<Contact | null>;\r\n /** The Historial tab's \"filter by person\" directory. Always Coventia's own org membership. */\r\n readonly listAgents: () => Promise<AgentsDirectory>;\r\n /** Phase A (transfer outcome relay) — polled by the transfer watch, never by UI directly. */\r\n readonly transferOutcomes: (params: { readonly since?: string }) => Promise<TransferOutcomesPage>;\r\n /** Phase B (presence-gated transfer picker) — opened by `presence-socket.ts`, never by UI directly. */\r\n readonly presence: PresenceSource;\r\n /** The caller's ringtone settings (custom-ringtone.md §4.4). Always Coventia's — no override. */\r\n readonly ringtones: RingtoneSource;\r\n}\r\n\r\n/**\r\n * Coventia's phone service, wired from `SoftphoneConfig.coventia`.\r\n *\r\n * **Synchronous object, lazily-loaded module.** The widget needs to know *at first render* whether\r\n * the Contactos and Historial tabs exist, and that is answered by the config alone — so this returns\r\n * immediately, while the ~2 kB adapter is fetched on first use. Core's own entry therefore carries no\r\n * HTTP and no `wa_id`, the same trick used for `jssip`, the flag SVGs and the phone-number metadata.\r\n *\r\n * One instance per softphone: the widget reads it back off `softphone.sources`, so it never builds a\r\n * second set with its own name cache.\r\n */\r\nexport function coventiaBackend(\r\n config: CoventiaBackendConfig,\r\n tokenSource: TokenSource,\r\n /** `SoftphoneConfig.apiBase`, consulted only when `config.baseUrl` is not set. */\r\n fallbackBaseUrl?: string,\r\n): BackendSources {\r\n // Resolved and validated EAGERLY, at `createSoftphone()` time, not deferred into `load()` below —\r\n // a host who configured `coventia` clearly wants contacts/history/caller ID, so a missing base URL\r\n // is a real misconfiguration, and the worst place to surface one is the first time a user opens\r\n // the Contactos tab, long after the phone has been happily reporting itself online.\r\n const baseUrl = config.baseUrl ?? fallbackBaseUrl;\r\n if (!baseUrl) {\r\n throw new Error(\r\n 'No base URL for the Coventia backend. Pass `coventia.baseUrl`, or a top-level `apiBase` on ' +\r\n '`createSoftphone` for it to fall back to.',\r\n );\r\n }\r\n\r\n let pending: Promise<BackendSources> | null = null;\r\n const load = (): Promise<BackendSources> => {\r\n pending ??= import('../coventia/sources.js').then((m) =>\r\n m.coventiaSources({ ...config, baseUrl, tokenSource }),\r\n );\r\n return pending;\r\n };\r\n\r\n return {\r\n contacts: async (params) => (await load()).contacts(params),\r\n history: async (params) => (await load()).history(params),\r\n resolveContact: async (number) => (await load()).resolveContact(number),\r\n listAgents: async () => (await load()).listAgents(),\r\n transferOutcomes: async (params) => (await load()).transferOutcomes(params),\r\n presence: async (callbacks) => (await load()).presence(callbacks),\r\n ringtones: {\r\n get: async (signal) => (await load()).ringtones.get(signal),\r\n setMine: async (id) => (await load()).ringtones.setMine(id),\r\n cached: async () => (await load()).ringtones.cached?.() ?? null,\r\n },\r\n };\r\n}\r\n","import { isBrowser } from './env.js';\r\n\r\n/**\r\n * Cross-tab leader election (SPEC §5.2, §5.3; `multi-tab-leader-election.md`).\r\n *\r\n * Zadarma's on-demand registration means every open tab that reacts to the same push independently\r\n * registers a SIP UA for the same extension — a race the SDK never controlled, because nothing told\r\n * one tab another existed. Confirmed live: with two tabs open, an inbound call rings in exactly one,\r\n * unpredictably which. `TabLock` is the seam that lets exactly one tab (\"the leader\") ever start the\r\n * real engine; every other tab (\"a follower\") stays dormant until its turn comes.\r\n */\r\nexport interface TabLock {\r\n /**\r\n * Resolves once this tab becomes leader — immediately if no other tab currently leads (the common\r\n * case: a single open tab, or an environment without Web Locks), or later, whenever (if ever)\r\n * another tab releases it. Never rejects; a cancelled attempt (via `release()`) simply never\r\n * resolves.\r\n */\r\n acquireLeadership(): Promise<void>;\r\n\r\n /** Relinquish leadership if held, or cancel a still-pending attempt. Safe to call more than once. */\r\n release(): void;\r\n}\r\n\r\nconst LOCK_NAME = 'coventia-softphone:leader';\r\n\r\n/**\r\n * True when the browser exposes the Web Locks API this coordination depends on.\r\n *\r\n * Safari added support in 15.4 (2022); Chrome and Firefox both predate that comfortably. Where it's\r\n * missing, `webLocksTabLock()` grants leadership immediately to every tab — today's actual\r\n * (uncoordinated, but functional) behaviour, not a regression.\r\n */\r\nexport function isWebLocksSupported(): boolean {\r\n return isBrowser() && typeof navigator.locks?.request === 'function';\r\n}\r\n\r\n/**\r\n * The real implementation. Constructed lazily by a caller, never at module scope (SPEC §10.3) — safe\r\n * to import on a server, and an environment without Web Locks pays nothing beyond one property read.\r\n *\r\n * One `TabLock` is meant to be acquired at most once per `Softphone` lifetime, from `start()` — see\r\n * `multi-tab-leader-election.md` §3.4.\r\n */\r\nexport function webLocksTabLock(lockName: string = LOCK_NAME): TabLock {\r\n const controller = new AbortController();\r\n let releaseHold: (() => void) | undefined;\r\n\r\n return {\r\n acquireLeadership(): Promise<void> {\r\n if (!isWebLocksSupported()) return Promise.resolve();\r\n\r\n return new Promise<void>((resolveAcquired) => {\r\n navigator.locks\r\n .request(lockName, { signal: controller.signal }, () => {\r\n // Granted — tell the caller NOW. How long the browser keeps the lock is governed by the\r\n // SEPARATE promise below, resolved later by release(), not by this one.\r\n resolveAcquired();\r\n return new Promise<void>((resolveHold) => {\r\n releaseHold = resolveHold;\r\n });\r\n })\r\n .catch(() => {\r\n // Aborted by release() before our turn came — expected cancellation, not a failure.\r\n // `acquireLeadership()`'s promise simply never resolves in this case.\r\n });\r\n });\r\n },\r\n\r\n release(): void {\r\n if (releaseHold) {\r\n const resolve = releaseHold;\r\n releaseHold = undefined;\r\n resolve();\r\n } else {\r\n controller.abort();\r\n }\r\n },\r\n };\r\n}\r\n","import type { TransferOutcomeEvent, TransferOutcomeSource, TransferOutcomesPage } from './types.js';\r\n\r\n/**\r\n * Poll cadence (spec §5.4, plan T5): 2 s for the first 10 s — a real success answers within ~4 s\r\n * (spec §8, F5) — then 5 s until the deadline. The observed no-answer ring is ~61 s (spec §8, F4); 90 s\r\n * leaves headroom without polling indefinitely.\r\n */\r\nconst FAST_INTERVAL_MS = 2_000;\r\nconst FAST_WINDOW_MS = 10_000;\r\nconst SLOW_INTERVAL_MS = 5_000;\r\nconst DEADLINE_MS = 90_000;\r\n\r\n/**\r\n * Digits only, at most six — deliberately generous next to Zadarma's own 3-digit extensions (spec §7,\r\n * plan §2). A `+`-prefixed or longer number is an outside line: no `NOTIFY_INTERNAL` will ever name it,\r\n * so there is nothing to poll for and starting a watch would just waste requests until the deadline.\r\n */\r\nconst EXTENSION_LIKE = /^\\d{1,6}$/;\r\n\r\nexport interface TransferWatchParams {\r\n readonly target: string;\r\n /** The ORIGINAL caller's number, for the eventual `TransferOutcomeEvent.caller` — null if unknown. */\r\n readonly callerNumber: string | null;\r\n readonly source: TransferOutcomeSource;\r\n /** Re-checked on every poll, not just at the start — see the leadership-handover note below. */\r\n readonly getIsLeader: () => boolean;\r\n readonly emit: (payload: TransferOutcomeEvent) => void;\r\n /** Overridable for tests; default to the real timers/clock. */\r\n readonly setTimeout?: typeof globalThis.setTimeout;\r\n readonly clearTimeout?: typeof globalThis.clearTimeout;\r\n readonly now?: () => number;\r\n}\r\n\r\n/**\r\n * Starts polling for one transfer's outcome (spec §5.4, plan T5). Returns a `stop()` — called from\r\n * `destroy()`, and safe to call again or after the watch already finished on its own (idempotent).\r\n *\r\n * **Each call owns its own private polling loop.** Deliberately not a single shared, mutable watch the\r\n * softphone reuses: `Call.transfer()` ends the call it was called on, so an agent is free to place and\r\n * transfer a SECOND call before the first watch's 90 s window elapses — multiple watches must be able\r\n * to run at once without one clobbering another's cursor or state. `no cross-attribution` is what this\r\n * buys, for the case the plan's acceptance actually describes: CONSECUTIVE transfers to the same\r\n * target, one fully resolved before the next begins. (Two watches racing for the exact same target at\r\n * the exact same time is a narrower case this design does not additionally solve — see the matching\r\n * step below.)\r\n */\r\nexport function startTransferWatch(params: TransferWatchParams): () => void {\r\n const {\r\n target,\r\n callerNumber,\r\n source,\r\n getIsLeader,\r\n emit,\r\n setTimeout: scheduleTimeout = globalThis.setTimeout,\r\n clearTimeout: cancelTimeout = globalThis.clearTimeout,\r\n now = () => Date.now(),\r\n } = params;\r\n\r\n // Outside line — nothing will ever be reported for it (spec §7). Checked before anything else so a\r\n // typed phone number never starts a single request.\r\n if (!EXTENSION_LIKE.test(target)) return () => {};\r\n // A follower tab never actually holds the call that just transferred (LeaderGate never lets one\r\n // place or receive a call at all) — this should be unreachable in practice, but costs nothing to\r\n // state explicitly rather than rely on that being true by construction elsewhere.\r\n if (!getIsLeader()) return () => {};\r\n\r\n let stopped = false;\r\n let timer: ReturnType<typeof globalThis.setTimeout> | null = null;\r\n let cursor: string | null = null;\r\n const startedAt = now();\r\n\r\n const stop = (): void => {\r\n stopped = true;\r\n if (timer !== null) {\r\n cancelTimeout(timer);\r\n timer = null;\r\n }\r\n };\r\n\r\n const finish = (status: 'connected' | 'not-connected'): void => {\r\n stop();\r\n emit({ status, target, caller: callerNumber, at: new Date(now()) });\r\n };\r\n\r\n const scheduleNext = (): void => {\r\n if (stopped) return;\r\n const elapsed = now() - startedAt;\r\n if (elapsed >= DEADLINE_MS) {\r\n // Deadline — stop silently. Spec §5.4: \"a timeout is never reported as success or failure.\"\r\n stop();\r\n return;\r\n }\r\n timer = scheduleTimeout(() => void poll(), elapsed < FAST_WINDOW_MS ? FAST_INTERVAL_MS : SLOW_INTERVAL_MS);\r\n };\r\n\r\n const poll = async (): Promise<void> => {\r\n if (stopped) return;\r\n if (!getIsLeader()) {\r\n // Leadership changed mid-watch (a tab handover) — this tab no longer holds the call that\r\n // transferred. Stop rather than keep polling and risk surfacing a notice nowhere it belongs.\r\n stop();\r\n return;\r\n }\r\n\r\n let page: TransferOutcomesPage;\r\n try {\r\n page = await source(cursor === null ? {} : { since: cursor });\r\n } catch {\r\n // A failed poll never ends the watch and never reaches the user or the host (plan T5's own\r\n // acceptance) — the transfer itself didn't fail, only this one attempt to check on it. Retry on\r\n // the normal cadence rather than inventing a separate backoff the spec doesn't call for.\r\n if (!stopped) scheduleNext();\r\n return;\r\n }\r\n if (stopped) return; // stop() may have run while the request was in flight\r\n\r\n if (cursor === null) {\r\n // The FIRST poll exists only to learn the starting cursor (spec §5.3's no-mis-attribution rule)\r\n // — anything already in the inbox predates this transfer and is deliberately never inspected.\r\n cursor = page.cursor;\r\n } else {\r\n cursor = page.cursor;\r\n const match = page.outcomes.find((o) => o.target === target);\r\n if (match) {\r\n finish(match.status);\r\n return;\r\n }\r\n }\r\n\r\n scheduleNext();\r\n };\r\n\r\n void poll(); // fires immediately — no reason to wait 2 s just to learn the starting cursor\r\n return stop;\r\n}\r\n","import type { PresenceEntry, PresenceSource } from './types.js';\r\n\r\n/**\r\n * Reconnect backoff (plan T6, spec §4.4/§4.7): doubling from 1 s, capped at 30 s, jittered so a\r\n * Cloud Run-wide cut (spec §4.2 — every connection capped at 60 min) doesn't send every agent's\r\n * softphone back in at the same instant. Not configurable — nothing about a specific value is part\r\n * of any contract, only \"backoff and jitter\" is (spec §4.4).\r\n */\r\nconst RECONNECT_BASE_MS = 1_000;\r\nconst RECONNECT_MAX_MS = 30_000;\r\n\r\nexport interface StartPresenceSocketParams {\r\n readonly source: PresenceSource;\r\n /**\r\n * Checked before the FIRST connection attempt, and again before every reconnect attempt after a\r\n * drop — the same two decision points `transfer-watch.ts`'s own `getIsLeader` is checked at (\"at\r\n * the start\" and \"on every poll\"). A persistent connection has no poll tick to hang a recheck off,\r\n * so those two moments — first attempt, each retry — are this module's equivalent: the places it is\r\n * already about to do something, rather than an invented timer whose only job would be to ask.\r\n *\r\n * Today's `SoftphoneImpl` grants leadership once, in `start()`, and only ever revokes it in\r\n * `stop()`/`destroy()` (`tabs.ts` — \"acquired at most once per Softphone lifetime\") — so in\r\n * practice `startPresenceSocket` is only ever called once leadership already holds, and this never\r\n * flips back to false out from under a running connection. Checked anyway, for the same reason\r\n * `transfer-watch.ts` does: cheap to state explicitly, and the caller who wires this up (plan T7)\r\n * is expected to call `stop()` the moment leadership or online-ness is lost, same as `#onTransfer`\r\n * already does for `#activeWatches` — this is a second line of defence, not the primary one.\r\n */\r\n readonly getIsLeader: () => boolean;\r\n /**\r\n * Fires after `snapshot` (the list, wholesale) and after every applied `delta` (the list, already\r\n * merged) — never the raw wire message. A caller wanting \"just this one agent\" over-reads a single\r\n * field; wanting \"everyone right now\" is the only shape a picker (T7) ever actually needs.\r\n */\r\n readonly onAgentsChanged: (agents: readonly PresenceEntry[]) => void;\r\n /** Overridable for tests; default to the real timers. */\r\n readonly setTimeout?: typeof globalThis.setTimeout;\r\n readonly clearTimeout?: typeof globalThis.clearTimeout;\r\n /** Overridable for tests — deterministic jitter. Defaults to `Math.random`. */\r\n readonly random?: () => number;\r\n}\r\n\r\nexport interface PresenceWatch {\r\n /**\r\n * Report this softphone's own on-call-ness. Safe to call as often as the caller likes — a no-op\r\n * unless the value actually changed since the last report (spec §4.4: \"never on an interval\").\r\n * While disconnected, the latest value is remembered and replayed on the next successful connect —\r\n * a call that started while the socket happened to be reconnecting must not be lost.\r\n */\r\n readonly reportCallState: (onCall: boolean) => void;\r\n /**\r\n * Stops for good — no further reconnect attempts. Idempotent, matching `startTransferWatch`'s own\r\n * `stop()` and `#activeWatches`'s cleanup convention.\r\n *\r\n * `{ bye: true }` is for a DELIBERATE stop only (the user turned the phone off): it tells the backend this\r\n * phone is gone so it can drop it at once. Every other stop — the state-driven teardown when the softphone\r\n * leaves `online` or loses leadership — must NOT say it, or an ordinary reconnect would read as the phone\r\n * being switched off.\r\n */\r\n readonly stop: (options?: PresenceStopOptions) => void;\r\n}\r\n\r\nexport interface PresenceStopOptions {\r\n readonly bye?: boolean;\r\n}\r\n\r\nconst NOOP_WATCH: PresenceWatch = { reportCallState: () => {}, stop: () => {} };\r\n\r\n/**\r\n * Starts (or immediately declines to start, for a follower tab) the phone-presence connection (plan\r\n * T6). Framework-free and transport-free: `source` is the only thing that knows how to actually open\r\n * a connection — Coventia's by default (`coventia/sources.ts`: mints a ticket, opens the WebSocket),\r\n * a host's own for a test double. This module owns only what is true of ANY such source: leader-\r\n * gating, reconnect-with-backoff, folding `snapshot`/`delta` into one live list, and de-duplicating\r\n * outgoing call-state reports.\r\n *\r\n * When `source` never resolves (a follower that should never have been given a source at all, or a\r\n * backend that is simply down) this produces no data and raises no error — spec §4.4's own success\r\n * criterion: the picker falls back to free text, nothing more.\r\n */\r\nexport function startPresenceSocket(params: StartPresenceSocketParams): PresenceWatch {\r\n const {\r\n source,\r\n getIsLeader,\r\n onAgentsChanged,\r\n setTimeout: scheduleTimeout = globalThis.setTimeout,\r\n clearTimeout: cancelTimeout = globalThis.clearTimeout,\r\n random = Math.random,\r\n } = params;\r\n\r\n // A follower never opens a connection at all — mirrors `startTransferWatch`'s own\r\n // `if (!getIsLeader()) return () => {}` bail (this file's own doc comment above explains why this\r\n // is the current design's normal, expected path for every tab but one, not an edge case).\r\n if (!getIsLeader()) return NOOP_WATCH;\r\n\r\n let stopped = false;\r\n let attempt = 0;\r\n let reconnectTimer: ReturnType<typeof globalThis.setTimeout> | null = null;\r\n let agents = new Map<string, PresenceEntry>();\r\n let lastReportedOnCall: boolean | null = null;\r\n let connection: Awaited<ReturnType<PresenceSource>> | null = null;\r\n // A deliberate stop that arrived while a connection was still being opened: it gets its goodbye the\r\n // moment it exists, instead of being closed silently and leaving a stamp to age out.\r\n let byeOnArrival = false;\r\n\r\n const stop = (options?: PresenceStopOptions): void => {\r\n if (stopped) return;\r\n stopped = true;\r\n if (reconnectTimer !== null) {\r\n cancelTimeout(reconnectTimer);\r\n reconnectTimer = null;\r\n }\r\n if (options?.bye) {\r\n byeOnArrival = true;\r\n connection?.bye?.();\r\n }\r\n connection?.close();\r\n connection = null;\r\n };\r\n\r\n const scheduleReconnect = (): void => {\r\n if (stopped) return;\r\n attempt += 1;\r\n const capped = Math.min(RECONNECT_BASE_MS * 2 ** (attempt - 1), RECONNECT_MAX_MS);\r\n // Full jitter: anywhere from 0 up to the capped delay, so a mass reconnect (a Cloud Run-wide cut)\r\n // spreads out instead of every socket retrying on the exact same clock tick.\r\n const delay = capped * random();\r\n reconnectTimer = scheduleTimeout(() => void connect(), delay);\r\n };\r\n\r\n const connect = async (): Promise<void> => {\r\n if (stopped) return;\r\n // Re-checked here too (see the param doc above) — the one moment after the very first attempt\r\n // where this module is about to do something and can cheaply ask first.\r\n if (!getIsLeader()) {\r\n stop();\r\n return;\r\n }\r\n\r\n let opened: Awaited<ReturnType<PresenceSource>>;\r\n try {\r\n opened = await source({\r\n onSnapshot: (snapshot) => {\r\n agents = new Map(snapshot.map((agent) => [agent.userId, agent]));\r\n onAgentsChanged([...agents.values()]);\r\n },\r\n onDelta: (agent) => {\r\n agents.set(agent.userId, agent);\r\n onAgentsChanged([...agents.values()]);\r\n },\r\n onClose: () => {\r\n // `connection === null` also covers \"not opened yet\" — safe because `source` is contracted\r\n // to resolve (open) BEFORE it ever calls `onClose` (a real WebSocket's own `open`/`close`\r\n // are always separate ticks; there is no such thing as a connection that closes before it\r\n // opens). A close reported before `connection` is set is therefore always OUR OWN — `stop()`\r\n // discarding this very attempt while it was still in flight — never a genuine drop.\r\n if (connection === null) return;\r\n connection = null;\r\n scheduleReconnect();\r\n },\r\n });\r\n } catch {\r\n // Ticket mint failed, the socket never opened, whatever — indistinguishable from a drop, so it\r\n // gets the same treatment: quiet retry, never surfaced (spec §4.4, success criterion #5).\r\n if (!stopped) scheduleReconnect();\r\n return;\r\n }\r\n\r\n if (stopped) {\r\n // `stop()` ran while the connection was still being opened — close it now that it exists rather\r\n // than leave it live with nothing left to receive its callbacks (saying goodbye first if the stop\r\n // was a deliberate one).\r\n if (byeOnArrival) opened.bye?.();\r\n opened.close();\r\n return;\r\n }\r\n\r\n connection = opened;\r\n attempt = 0; // a successful connect resets the backoff for the NEXT drop, not this one\r\n if (lastReportedOnCall !== null) connection.reportCallState(lastReportedOnCall);\r\n };\r\n\r\n void connect();\r\n\r\n return {\r\n reportCallState: (onCall) => {\r\n if (onCall === lastReportedOnCall) return;\r\n lastReportedOnCall = onCall;\r\n connection?.reportCallState(onCall);\r\n },\r\n stop,\r\n };\r\n}\r\n","import { Emitter } from './emitter.js';\r\nimport { CallImpl } from './call-impl.js';\r\nimport { coventiaBackend, type BackendSources } from './backend.js';\r\nimport { cachedTokenSource } from './token-cache.js';\r\nimport { CallEvent, SoftphoneEvent, type SoftphoneEventMap } from './events.js';\r\nimport { isBrowser } from './env.js';\r\nimport { webLocksTabLock, type TabLock } from './tabs.js';\r\nimport { startTransferWatch } from './transfer-watch.js';\r\nimport { startPresenceSocket, type PresenceStopOptions, type PresenceWatch } from './presence-socket.js';\r\nimport type { AudioDevices, Softphone } from './softphone.js';\r\nimport type { CallEngine, EngineCall } from './engine.js';\r\nimport { isOnCallState, type Call } from './call.js';\r\nimport { isFatal, type FatalSoftphoneError, type SoftphoneError } from './errors.js';\r\nimport type { CallOptions, PresenceEntry, SoftphoneConfig, SoftphoneState } from './types.js';\r\n\r\n/**\r\n * A page may hold ONE live softphone. A softphone owns singleton browser resources — the microphone,\r\n * a registration, an RTCPeerConnection — and a second instance means a second `getUserMedia` and two\r\n * registrations fighting over one extension. Daily throws on a second call object for the same\r\n * reason; we follow it.\r\n *\r\n * Module-scope, but not a browser global, so it is SSR-safe (SPEC §10.2): it is a plain boolean that\r\n * flips in `start()`, never at import.\r\n */\r\nlet liveInstance = false;\r\n\r\n/**\r\n * How long the presence socket survives the softphone being unreachable before it is torn down\r\n * (presence-client-reporting.md §4.1, T7). The softphone leaves `online` for a moment all the time — the\r\n * push socket is rebuilt every ~20 s while a tab is hidden, and the gap measured live was ~0.5 s — and each\r\n * of those used to close and reopen the presence socket: a ticket, a snapshot (two Supabase queries), a\r\n * wiped roster and a burst of broadcasts, for nothing. Well under the backend's 30 s freshness, so a genuine\r\n * outage still reads *Desconectado* soon after; well over any normal gap.\r\n */\r\nconst PRESENCE_TEARDOWN_GRACE_MS = 10_000;\r\n\r\n/**\r\n * The real device surface (SPEC §7 Phase 2). Live `enumerateDevices()`, reactive to the browser's own\r\n * `devicechange`, input switching delegated to the engine (`CallEngine.setInputDevice` — the SIP\r\n * engine implements it; a mock with nothing real to swap simply omits it), output switching entirely\r\n * local (there is no engine involvement at all — see `AudioDevices.setOutput`'s own comment: routing\r\n * already-decoded audio needs no SIP/WebRTC layer underneath it).\r\n *\r\n * SSR-safe like everything else under `core/` (SPEC §10.2): every browser global is read lazily —\r\n * never at module scope — and gated behind `isBrowser()`.\r\n */\r\nclass BrowserAudioDevices implements AudioDevices {\r\n inputs: readonly MediaDeviceInfo[] = [];\r\n outputs: readonly MediaDeviceInfo[] = [];\r\n currentInputId: string | null = null;\r\n currentOutputId: string | null = null;\r\n currentRingtoneOutputId: string | null = null;\r\n readonly canSelectOutput: boolean;\r\n readonly canSelectRingtoneOutput: boolean;\r\n\r\n readonly #engine: CallEngine;\r\n readonly #onChange: () => void;\r\n /**\r\n * What the user CHOSE, remembered across reloads — distinct from the `current*` fields, which are\r\n * what is usable right now. Keeping them apart is what stops a fresh page load from wiping a saved\r\n * choice: until the microphone has been granted, Chrome enumerates every device with a blank id, so\r\n * \"is the saved device in the list?\" is no on every first enumeration. The preference survives\r\n * that; `current*` simply reads `null` until the device is really there.\r\n */\r\n #preferred: StoredDevices;\r\n /** The input the engine was last told about — it starts on the system default (`null`). */\r\n #engineInputId: string | null = null;\r\n\r\n constructor(engine: CallEngine, onChange: () => void) {\r\n this.#engine = engine;\r\n this.#onChange = onChange;\r\n this.canSelectOutput =\r\n isBrowser() &&\r\n typeof HTMLMediaElement !== 'undefined' &&\r\n 'setSinkId' in HTMLMediaElement.prototype;\r\n // The ring is routed through an <audio> element where nothing better exists (custom-ringtone.md\r\n // §4.5), so it needs exactly what the call speaker needs.\r\n this.canSelectRingtoneOutput = this.canSelectOutput;\r\n this.#preferred = readStoredDevices();\r\n // Usable at once for anything that does not need the list (applying an output id before the first\r\n // enumeration lands); `refresh()` re-validates all three against the real devices.\r\n this.currentInputId = this.#preferred.input;\r\n this.currentOutputId = this.#preferred.output;\r\n this.currentRingtoneOutputId = this.#preferred.ringtone;\r\n\r\n if (isBrowser() && navigator.mediaDevices) {\r\n void this.refresh();\r\n // No teardown: `mediaDevices` is the FRAME's own object, not a resource this instance opened,\r\n // and this instance lives exactly as long as the Softphone does — there is nothing to leak.\r\n navigator.mediaDevices.addEventListener?.('devicechange', () => void this.refresh());\r\n }\r\n }\r\n\r\n async refresh(): Promise<void> {\r\n if (!isBrowser() || typeof navigator.mediaDevices?.enumerateDevices !== 'function') return;\r\n let all: readonly MediaDeviceInfo[];\r\n try {\r\n all = await navigator.mediaDevices.enumerateDevices();\r\n } catch {\r\n // Rare, but real — a locked-down permissions policy or an odd polyfill can reject this. The\r\n // lists simply stay whatever they were; there is no better fallback than \"unchanged.\"\r\n return;\r\n }\r\n this.inputs = all.filter((d) => d.kind === 'audioinput');\r\n this.outputs = all.filter((d) => d.kind === 'audiooutput');\r\n // Each current value is the preference when that device is really in the list, else `null`: a\r\n // selection pointing at a device that just vanished (unplugged) must not keep claiming it is still\r\n // selected — the UI would show a \"chosen\" option that no longer exists in either list — and one\r\n // that comes back (plugged in again) is selected again without the user choosing it twice.\r\n this.currentInputId = usable(this.#preferred.input, this.inputs);\r\n this.currentOutputId = usable(this.#preferred.output, this.outputs);\r\n this.currentRingtoneOutputId = usable(this.#preferred.ringtone, this.outputs);\r\n this.#onChange();\r\n // The engine opens the microphone itself, so a remembered input it has not been told about yet — a\r\n // reload, or the headset coming back — must reach it. Only on the way IN: what happens to a live\r\n // call's mic when a device is pulled is unchanged by this.\r\n if (this.currentInputId && this.currentInputId !== this.#engineInputId) {\r\n await this.#tellEngine(this.currentInputId);\r\n }\r\n }\r\n\r\n async setInput(deviceId: string | null): Promise<void> {\r\n this.currentInputId = deviceId;\r\n this.#remember({ input: deviceId });\r\n this.#onChange();\r\n await this.#tellEngine(deviceId);\r\n }\r\n\r\n async #tellEngine(deviceId: string | null): Promise<void> {\r\n this.#engineInputId = deviceId;\r\n // Optional on the interface — a mock engine with no real media has nothing to swap and simply\r\n // does not implement it.\r\n await this.#engine.setInputDevice?.(deviceId);\r\n }\r\n\r\n async setOutput(deviceId: string | null): Promise<void> {\r\n this.currentOutputId = deviceId;\r\n this.#remember({ output: deviceId });\r\n this.#onChange();\r\n // Nothing to await here: applying it is the UI layer's job (`useCallAudio`'s `setSinkId` on the\r\n // remote-audio element), reached only through `SoftphoneEvent.DeviceChange` — core/ cannot touch\r\n // `document` itself (SPEC §10.2).\r\n }\r\n\r\n async setRingtoneOutput(deviceId: string | null): Promise<void> {\r\n this.currentRingtoneOutputId = deviceId;\r\n this.#remember({ ringtone: deviceId });\r\n this.#onChange();\r\n // Same as `setOutput`: the ring is routed by the `/ui` layer, on `SoftphoneEvent.DeviceChange`.\r\n }\r\n\r\n #remember(patch: Partial<StoredDevices>): void {\r\n this.#preferred = { ...this.#preferred, ...patch };\r\n writeStoredDevices(this.#preferred);\r\n }\r\n}\r\n\r\n/** The remembered device choices (`AudioDevices.currentInputId`'s note). Ids, or `null` = system default. */\r\ninterface StoredDevices {\r\n readonly input: string | null;\r\n readonly output: string | null;\r\n readonly ringtone: string | null;\r\n}\r\n\r\nconst DEVICES_KEY = 'coventia-softphone:devices';\r\nconst NO_DEVICES: StoredDevices = { input: null, output: null, ringtone: null };\r\n\r\n/** `id` when that device is in `list`, else `null`. */\r\nfunction usable(id: string | null, list: readonly MediaDeviceInfo[]): string | null {\r\n return id && list.some((d) => d.deviceId === id) ? id : null;\r\n}\r\n\r\n/**\r\n * Never throws: storage can be absent (SSR), blocked (private mode, a sandboxed iframe — the getter\r\n * itself throws there), or hold anything another script wrote. Anything unusable reads as \"no choice\".\r\n */\r\nfunction readStoredDevices(): StoredDevices {\r\n if (!isBrowser()) return NO_DEVICES;\r\n try {\r\n const raw: unknown = JSON.parse(globalThis.localStorage?.getItem(DEVICES_KEY) ?? 'null');\r\n if (!raw || typeof raw !== 'object') return NO_DEVICES;\r\n const pick = (v: unknown): string | null => (typeof v === 'string' && v ? v : null);\r\n const r = raw as Record<string, unknown>;\r\n return { input: pick(r.input), output: pick(r.output), ringtone: pick(r.ringtone) };\r\n } catch {\r\n return NO_DEVICES;\r\n }\r\n}\r\n\r\nfunction writeStoredDevices(devices: StoredDevices): void {\r\n if (!isBrowser()) return;\r\n try {\r\n globalThis.localStorage?.setItem(DEVICES_KEY, JSON.stringify(devices));\r\n } catch {\r\n /* blocked or full — the choice still holds for this page, as it always did */\r\n }\r\n}\r\n\r\nexport class SoftphoneImpl extends Emitter<SoftphoneEventMap> implements Softphone {\r\n #state: SoftphoneState = 'stopped';\r\n #lastError: FatalSoftphoneError | null = null;\r\n #activeCall: CallImpl | null = null;\r\n #destroyed = false;\r\n #startedHere = false;\r\n #isLeader = false;\r\n readonly #engine: CallEngine;\r\n readonly #tabLock: TabLock;\r\n readonly #resolveContact: SoftphoneConfig['resolveContact'];\r\n /** Contacts + history, whenever there is a Coventia backend to reach. The widget reads these. */\r\n readonly sources: BackendSources | null;\r\n readonly audio: AudioDevices;\r\n /**\r\n * Every in-flight transfer-outcome watch (Phase A), so `destroy()` can stop all of them. Never\r\n * pruned as individual watches finish on their own — `stop()` is idempotent and cheap to call again,\r\n * and a session accumulating a handful of closures for however many transfers it makes is not worth\r\n * the bookkeeping a self-removing set would add.\r\n */\r\n readonly #activeWatches = new Set<() => void>();\r\n /**\r\n * Phase B (presence-gated transfer picker). ONE long-lived watch, not a member of `#activeWatches`\r\n * — that set is per-transfer instances a live call spawns and forgets; this is a single background\r\n * connection tied to the softphone's own lifecycle, started and stopped by `#syncPresenceWatch`\r\n * rather than by any one call.\r\n */\r\n #presenceWatch: PresenceWatch | null = null;\r\n /** A pending state-driven teardown of the watch — see `PRESENCE_TEARDOWN_GRACE_MS`. */\r\n #presenceTeardownTimer: ReturnType<typeof setTimeout> | null = null;\r\n #presenceAgents: readonly PresenceEntry[] = [];\r\n\r\n /**\r\n * `tabLock` defaults to the real Web Locks implementation — not part of `SoftphoneConfig` (an\r\n * internal seam, not something an integrator tunes). Tests construct this class directly with a\r\n * fake for deterministic control; `createSoftphone()` never passes a third argument, so every\r\n * existing test that goes through it is unaffected (`multi-tab-leader-election.md` §3.8).\r\n */\r\n constructor(engine: CallEngine, config: SoftphoneConfig, tabLock: TabLock = webLocksTabLock()) {\r\n super();\r\n this.#engine = engine;\r\n this.#tabLock = tabLock;\r\n // Built here, not as a field initializer — it needs `this.#engine` (set the line above) and\r\n // `this.emit` (inherited from `Emitter`, not available until after `super()`), so it cannot be a\r\n // default value evaluated before either exists.\r\n this.audio = new BrowserAudioDevices(this.#engine, () =>\r\n this.emit(SoftphoneEvent.DeviceChange, undefined),\r\n );\r\n // ONE set of sources, built here so the widget can read them back instead of constructing a\r\n // second set from identical config (two name caches, two of every request).\r\n //\r\n // `apiBase` ALONE is enough to build these — `coventia` is for OVERRIDING something (your own\r\n // contacts function, an `organizationId`, a `baseUrl` that differs from the engine's), never for\r\n // switching the feature on. It used to be the switch: omitting `coventia` meant no Contactos tab\r\n // even with `apiBase` set for calling, which is the wrong default for OUR OWN backend's own\r\n // contacts/history/caller ID — a host would have to know to add an otherwise-empty `coventia: {}`\r\n // to get something this SDK should simply give them.\r\n //\r\n // One token for every request the sources make, instead of one per request — see\r\n // `cachedTokenSource`. Idempotent, so wrapping a source the engine already wrapped is free and\r\n // they share the same entry.\r\n this.sources =\r\n config.coventia || config.apiBase\r\n ? coventiaBackend(config.coventia ?? {}, cachedTokenSource(config.tokenSource), config.apiBase)\r\n : null;\r\n this.#resolveContact = config.resolveContact ?? this.sources?.resolveContact;\r\n this.#engine.onIncoming((engineCall) => this.#onIncoming(engineCall));\r\n this.#engine.onStateChange((state) => this.#setState(state));\r\n // Optional on the interface — an engine that never reports failures (the mock) simply has none.\r\n this.#engine.onError?.((error) => this.#onError(error));\r\n }\r\n\r\n get state(): SoftphoneState {\r\n return this.#state;\r\n }\r\n get lastError(): FatalSoftphoneError | null {\r\n return this.#lastError;\r\n }\r\n get isLeader(): boolean {\r\n return this.#isLeader;\r\n }\r\n get activeCall(): Call | null {\r\n return this.#activeCall;\r\n }\r\n get presence(): readonly PresenceEntry[] {\r\n return this.#presenceAgents;\r\n }\r\n get canPlayAudio(): boolean {\r\n // The autoplay-recovery machinery is Phase 2; the mock has no audio to block.\r\n return true;\r\n }\r\n async startAudio(): Promise<void> {\r\n /* autoplay recovery lands in Phase 2 */\r\n }\r\n\r\n async start(): Promise<void> {\r\n this.#assertUsable();\r\n if (this.#startedHere) return;\r\n if (liveInstance) {\r\n throw new Error(\r\n 'A Softphone is already running on this page. Construct one and share it; call destroy() before starting another. (SPEC §5.3 / error engine/duplicate-instance)',\r\n );\r\n }\r\n liveInstance = true;\r\n this.#startedHere = true;\r\n // Set BEFORE acquiring leadership, not after: `'starting'` means \"start() was called and is in\r\n // progress\", which includes waiting behind another tab as a real sub-phase of starting up — not\r\n // only the part after leadership is granted. This is what lets a host tell \"never started\" apart\r\n // from \"started, currently a follower\" (both otherwise look identical: `isLeader === false`,\r\n // and — had this stayed `'stopped'` here — `state === 'stopped'` too). The widget's `LeaderGate`\r\n // depends on this distinction (`multi-tab-leader-election.md` §3.4, §4).\r\n this.#setState('starting');\r\n\r\n // Resolves now for a single open tab (the common case — imperceptibly fast, nothing else holds\r\n // the lock), or later, whenever another tab releases it. See `multi-tab-leader-election.md` §3.4\r\n // for why this is NOT split into an immediate/deferred branch: nothing today awaits `start()`\r\n // closely enough to need one, and stop()/destroy() called while this is still pending (a\r\n // follower torn down before ever leading) simply leaves it permanently unresolved — see stop().\r\n await this.#tabLock.acquireLeadership();\r\n if (this.#destroyed) return; // torn down while waiting to lead — do not start the engine now\r\n this.#setLeader(true);\r\n await this.#engine.start();\r\n }\r\n\r\n async stop(): Promise<void> {\r\n if (!this.#startedHere) return;\r\n // BEFORE the engine stops: its own `stopped` state would otherwise tear the presence watch down as an\r\n // ordinary state-driven teardown, with no goodbye.\r\n this.#sayGoodbye();\r\n this.#tabLock.release();\r\n if (this.#isLeader) {\r\n await this.#engine.stop();\r\n if (this.#activeCall) await this.#activeCall.hangup().catch(() => {});\r\n }\r\n this.#setLeader(false);\r\n this.#releaseInstance();\r\n this.#setState('stopped');\r\n }\r\n\r\n async destroy(): Promise<void> {\r\n if (this.#destroyed) return;\r\n this.#destroyed = true;\r\n // Every timer this softphone owns beyond the engine's own — stopped first and unconditionally,\r\n // independent of #engine/#tabLock state below.\r\n for (const stopWatch of this.#activeWatches) stopWatch();\r\n this.#activeWatches.clear();\r\n this.#sayGoodbye();\r\n await this.stop().catch(() => {});\r\n this.#setState('destroyed');\r\n this.clear();\r\n }\r\n\r\n async call(destination: string, options?: CallOptions): Promise<Call> {\r\n this.#assertUsable();\r\n if (this.#state !== 'online' && this.#state !== 'offline' && this.#state !== 'starting') {\r\n throw new Error(`Cannot place a call while softphone is '${this.#state}'.`);\r\n }\r\n if (this.#activeCall) {\r\n throw new Error('A call is already in progress. Hang up before placing another.');\r\n }\r\n const engineCall = await this.#engine.placeCall(destination, options);\r\n return this.#adopt(engineCall);\r\n }\r\n\r\n // ── internals ───────────────────────────────────────────────────────────────\r\n #onIncoming(engineCall: EngineCall): void {\r\n if (this.#activeCall) {\r\n // Busy: the engine is responsible for the 486. Nothing to surface.\r\n void engineCall.reject().catch(() => {});\r\n return;\r\n }\r\n const call = this.#adopt(engineCall);\r\n this.emit(SoftphoneEvent.IncomingCall, call);\r\n }\r\n\r\n #adopt(engineCall: EngineCall): CallImpl {\r\n const call = new CallImpl(\r\n engineCall,\r\n (event, error) => this.#reportListenerError('call', event, error),\r\n (message) => this.#reportInternalError(message),\r\n (target, callerNumber) => this.#onTransfer(target, callerNumber),\r\n );\r\n this.#activeCall = call;\r\n\r\n // Presence: tell the backend whether THIS softphone is on a call, from the first state the call has and on\r\n // every change after it (presence-client-reporting.md §3.1). The call's own `ended` state is what reports\r\n // \"off\" — it is emitted before `Ended`, so the backend hears it the instant the call stops.\r\n const offState = call.on(CallEvent.StateChanged, () => this.#syncCallState());\r\n this.#syncCallState();\r\n\r\n // Clear the slot when the call ends, so the next call can begin.\r\n const off = call.on(CallEvent.Ended, () => {\r\n off();\r\n offState();\r\n if (this.#activeCall === call) {\r\n this.#activeCall = null;\r\n this.emit(SoftphoneEvent.ActiveCallChanged, { call: null });\r\n this.#syncCallState();\r\n }\r\n });\r\n\r\n // Resolve the caller's name from the host's contacts and update the call in place. The provider's\r\n // SIP display name is the line label, not the caller (SPEC §4), so a resolved contact wins.\r\n if (this.#resolveContact) {\r\n void this.#resolveContact(engineCall.remoteNumber)\r\n .then((contact) => {\r\n if (contact && this.#activeCall === call) call.setRemoteContact(contact);\r\n })\r\n .catch(() => {});\r\n }\r\n\r\n // Fires for both inbound and outbound (both reach here) — what a UI's useActiveCall binds to.\r\n this.emit(SoftphoneEvent.ActiveCallChanged, { call });\r\n return call;\r\n }\r\n\r\n #setState(state: SoftphoneState): void {\r\n if (state === this.#state) return;\r\n const previous = this.#state;\r\n this.#state = state;\r\n /*\r\n * Reaching `online` is the only evidence we get that a fatal condition has passed, so it is what\r\n * clears it. Deliberately not a timer and not user input: neither is evidence of anything, and a\r\n * cleared error over a phone that still cannot call is worse than no error at all.\r\n *\r\n * For Zadarma `online` means the push socket is up, which is weaker than \"everything works\" — a\r\n * token could still be refused on the next key fetch. That self-corrects: the fetch fails, the\r\n * error is re-emitted, `lastError` is set again. An explanation that briefly lifts and returns\r\n * beats one that never lifts and needs a reload.\r\n */\r\n if (state === 'online') this.#lastError = null;\r\n this.emit(SoftphoneEvent.StateChanged, { state, previous });\r\n this.#syncPresenceWatch();\r\n }\r\n\r\n /**\r\n * Whether this softphone is on a call right now, as presence means it (`isOnCallState`). There is a single\r\n * active-call slot, and a consult leg is a child of that call, so the parent staying alive covers a consult.\r\n */\r\n #isOnCall(): boolean {\r\n return this.#activeCall !== null && isOnCallState(this.#activeCall.state);\r\n }\r\n\r\n /**\r\n * Reports the current on-a-call value to the backend. The watch de-duplicates (it only sends on change) and\r\n * remembers the latest value to replay after a reconnect, so this can be called on every transition without\r\n * ceremony. A no-op while there is no presence watch (not online, not the leader, no backend configured).\r\n */\r\n #syncCallState(): void {\r\n this.#presenceWatch?.reportCallState(this.#isOnCall());\r\n }\r\n\r\n #setLeader(isLeader: boolean): void {\r\n if (isLeader === this.#isLeader) return;\r\n this.#isLeader = isLeader;\r\n this.emit(SoftphoneEvent.LeaderChanged, { isLeader });\r\n this.#syncPresenceWatch();\r\n }\r\n\r\n /**\r\n * Phase B (presence-gated transfer picker, `docs/specs/transfer-presence-picker.md` plan T6/T7).\r\n * The ONE place that decides whether the presence connection should be running right now — called\r\n * after every `#setState`/`#setLeader` transition rather than left to either alone, since either\r\n * one flipping can change the answer. No-ops entirely when no Coventia backend is configured\r\n * (`sources` is null) — same guard `#onTransfer` uses, and for the same reason: nothing to open.\r\n *\r\n * `presence-socket.ts`'s own `getIsLeader` is checked at start and before each reconnect attempt,\r\n * not on a poll tick the way `startTransferWatch`'s is — see that module's own doc comment for why.\r\n * This still passes `getIsLeader: () => this.#isLeader` for that narrower, defensive recheck; the\r\n * BROAD online/leader gate — whether to be connected AT ALL — is owned entirely here, the same\r\n * split `#onTransfer` already has with `startTransferWatch`.\r\n *\r\n * One exception to \"tear down the moment `online` is lost\": a leader that is merely `offline` or\r\n * `reconnecting` keeps its watch for `PRESENCE_TEARDOWN_GRACE_MS` — a blip shorter than that never touches\r\n * the presence socket at all. Anything else (stopped, starting, destroyed, or no longer the leader) still\r\n * tears it down at once: those are not blips, and a follower must never hold a connection.\r\n */\r\n #syncPresenceWatch(): void {\r\n const eligible = Boolean(this.sources) && this.#isLeader;\r\n const shouldRun = eligible && this.#state === 'online';\r\n const blip = eligible && (this.#state === 'offline' || this.#state === 'reconnecting');\r\n\r\n if (!blip) this.#cancelPresenceTeardown(); // back online, or no longer a mere blip\r\n if (blip && this.#presenceWatch !== null) {\r\n this.#schedulePresenceTeardown();\r\n return;\r\n }\r\n if (shouldRun === (this.#presenceWatch !== null)) return;\r\n\r\n if (shouldRun) {\r\n this.#presenceWatch = startPresenceSocket({\r\n source: this.sources!.presence,\r\n getIsLeader: () => this.#isLeader,\r\n onAgentsChanged: (agents) => {\r\n this.#presenceAgents = agents;\r\n this.emit(SoftphoneEvent.PresenceChanged, { agents });\r\n },\r\n });\r\n // A NEW watch starts knowing nothing, and the softphone leaves and re-enters `online` all the time\r\n // (the hidden-tab push-socket rebuild does it every ~20 s): tell it what is true right now —\r\n // INCLUDING \"not on a call\" — so a call already in progress is never forgotten at a restart, and the\r\n // backend always learns this client reports. Presence-client-reporting.md §3.2.\r\n this.#syncCallState();\r\n } else {\r\n this.#stopPresence();\r\n }\r\n }\r\n\r\n /**\r\n * Starts the grace period before an `offline` / `reconnecting` leader loses its presence socket. Idempotent\r\n * on purpose: a second notification inside the same blip must NOT push the deadline back.\r\n */\r\n #schedulePresenceTeardown(): void {\r\n if (this.#presenceTeardownTimer !== null) return;\r\n this.#presenceTeardownTimer = setTimeout(() => {\r\n this.#presenceTeardownTimer = null;\r\n // Still unreachable after the grace: that is an outage, not a blip. (Back online or no longer a\r\n // leader would already have cancelled this; the check is the belt to that braces.)\r\n if (this.#state === 'online' && this.#isLeader) return;\r\n this.#stopPresence();\r\n }, PRESENCE_TEARDOWN_GRACE_MS);\r\n }\r\n\r\n #cancelPresenceTeardown(): void {\r\n if (this.#presenceTeardownTimer === null) return;\r\n clearTimeout(this.#presenceTeardownTimer);\r\n this.#presenceTeardownTimer = null;\r\n }\r\n\r\n /**\r\n * Stops the presence watch and forgets its roster. A STATE-DRIVEN teardown (the softphone left `online`\r\n * for good, or lost leadership) passes nothing and says no goodbye; only a deliberate stop passes `bye`.\r\n * Any pending grace-period teardown is cancelled — there is nothing left for it to do.\r\n */\r\n #stopPresence(options?: PresenceStopOptions): void {\r\n this.#cancelPresenceTeardown();\r\n if (!this.#presenceWatch) return;\r\n this.#presenceWatch.stop(options);\r\n this.#presenceWatch = null;\r\n // A stale list from the connection that just closed must not keep being read as current —\r\n // mirrors `lastError`'s own \"cleared only on real evidence\" rule, just for the opposite fact:\r\n // losing the connection IS evidence there is nothing live left to show.\r\n this.#presenceAgents = [];\r\n this.emit(SoftphoneEvent.PresenceChanged, { agents: [] });\r\n }\r\n\r\n /**\r\n * A DELIBERATE stop (`stop()` / `destroy()`): say any call is over, then say goodbye, so the backend drops\r\n * this phone at once instead of waiting out the close grace. Idempotent — a second call finds no watch.\r\n */\r\n #sayGoodbye(): void {\r\n this.#presenceWatch?.reportCallState(false);\r\n this.#stopPresence({ bye: true });\r\n }\r\n\r\n /**\r\n * Phase A (transfer outcome relay, `docs/specs/transfer-outcome-relay.md`) — `CallImpl.transfer()`'s\r\n * internal seam calls this. No-ops entirely when no Coventia backend is configured (`sources` is\r\n * null): there is then nothing to poll, and behaviour is exactly what it was before this feature\r\n * existed (spec §5.4).\r\n */\r\n #onTransfer(target: string, callerNumber: string | null): void {\r\n if (!this.sources) return;\r\n const stopWatch = startTransferWatch({\r\n target,\r\n callerNumber,\r\n source: this.sources.transferOutcomes,\r\n getIsLeader: () => this.#isLeader,\r\n emit: (payload) => this.emit(SoftphoneEvent.TransferOutcome, payload),\r\n });\r\n this.#activeWatches.add(stopWatch);\r\n }\r\n\r\n /**\r\n * Re-emit an engine failure to the host, and remember it if it is fatal.\r\n *\r\n * Every error is emitted; only fatal ones are retained. A non-fatal error is a fact about one call\r\n * — the remote party was busy — and keeping it as softphone state would make a working phone look\r\n * broken to anything that renders `lastError`.\r\n */\r\n #onError(error: SoftphoneError): void {\r\n if (isFatal(error)) this.#lastError = error;\r\n this.emit(SoftphoneEvent.Error, error);\r\n }\r\n\r\n #releaseInstance(): void {\r\n if (this.#startedHere) {\r\n liveInstance = false;\r\n this.#startedHere = false;\r\n }\r\n }\r\n\r\n #assertUsable(): void {\r\n if (this.#destroyed) {\r\n throw new Error('This Softphone has been destroyed. Construct a new one. (engine/destroyed)');\r\n }\r\n }\r\n\r\n #reportListenerError(source: string, event: string | number | symbol, error: unknown): void {\r\n // A throwing host listener must never take the engine down. Surface it, don't swallow it.\r\n console.error(`[softphone] a ${source} '${String(event)}' listener threw:`, error);\r\n }\r\n\r\n #reportInternalError(message: string): void {\r\n console.error(`[softphone] ${message}`);\r\n }\r\n}\r\n\r\n/** Test-only reset of the page-singleton guard, so suites don't leak the flag between cases. */\r\nexport function __resetInstanceGuardForTests(): void {\r\n liveInstance = false;\r\n}\r\n","import type { CallEngine, EngineCall } from './engine.js';\r\nimport type { CallOptions, SoftphoneState, TokenSource } from './types.js';\r\nimport type { SoftphoneError } from './errors.js';\r\n\r\nexport interface DefaultProviderConfig {\r\n readonly apiBase: string;\r\n readonly tokenSource: TokenSource;\r\n readonly organizationId?: string | undefined;\r\n}\r\n\r\n/**\r\n * A subscription list that may not have a real target yet.\r\n *\r\n * `defaultZadarmaEngine` below has THREE of these (`onIncoming`, `onStateChange`, `onError`), all\r\n * needing the identical, slightly tricky behaviour: a listener registered before the real engine\r\n * exists must still receive events once it does, AND unsubscribing must work correctly regardless of\r\n * whether it happens before or after that engine shows up. Three independent copies of this is three\r\n * independent chances to get the \"unsubscribe after the real engine attached\" case wrong — silently,\r\n * since nothing in this file's one current caller (`SoftphoneImpl`) ever unsubscribes these three, so\r\n * a bug here would only bite a future caller. One small class, three call sites, no reason to write\r\n * (and maintain) the replay/unsubscribe logic three times.\r\n */\r\nclass LazyRelay<L extends (...args: never[]) => void> {\r\n readonly #listeners = new Set<L>();\r\n readonly #realOff = new Map<L, () => void>();\r\n #target: { on(listener: L): () => void } | null = null;\r\n\r\n subscribe(listener: L): () => void {\r\n this.#listeners.add(listener);\r\n if (this.#target) this.#realOff.set(listener, this.#target.on(listener));\r\n return () => {\r\n this.#listeners.delete(listener);\r\n this.#realOff.get(listener)?.();\r\n this.#realOff.delete(listener);\r\n };\r\n }\r\n\r\n /** Called once the real engine exists — replays every listener registered before now onto it. */\r\n attach(target: { on(listener: L): () => void }): void {\r\n this.#target = target;\r\n for (const listener of this.#listeners) this.#realOff.set(listener, target.on(listener));\r\n }\r\n}\r\n\r\n/**\r\n * The default call engine: Zadarma, loaded lazily.\r\n *\r\n * Returns a `CallEngine` SYNCHRONOUSLY — `createSoftphone` must stay synchronous and SSR-safe even\r\n * when `provider` is omitted — and defers the real work to the first genuine use (`start()` or\r\n * `placeCall()`). Only then does it dynamically `import('../transports/zadarma-engine.js')`, which is\r\n * what actually reaches jssip and browser globals. Exactly the trick `coventiaBackend` (`backend.ts`)\r\n * already uses for contacts/history; applied here to the engine instead.\r\n *\r\n * The import specifier is a STRING LITERAL, not a variable — `check-imports.mjs` enforces this across\r\n * the whole build, after a variable specifier here once shipped a bare specifier no bundler could\r\n * resolve, which typechecked, passed 211 tests (all against fakes), and broke only in a real browser.\r\n */\r\nexport function defaultZadarmaEngine(config: DefaultProviderConfig): CallEngine {\r\n let real: CallEngine | null = null;\r\n let pending: Promise<CallEngine> | null = null;\r\n\r\n const incoming = new LazyRelay<(call: EngineCall) => void>();\r\n const stateChange = new LazyRelay<(state: SoftphoneState) => void>();\r\n const error = new LazyRelay<(error: SoftphoneError) => void>();\r\n\r\n function load(): Promise<CallEngine> {\r\n pending ??= import('../transports/zadarma-engine.js')\r\n .then(({ createZadarmaEngine }) => {\r\n const engine = createZadarmaEngine(config);\r\n incoming.attach({ on: (l) => engine.onIncoming(l) });\r\n stateChange.attach({ on: (l) => engine.onStateChange(l) });\r\n error.attach({ on: (l) => engine.onError?.(l) ?? (() => {}) });\r\n real = engine;\r\n return engine;\r\n })\r\n .catch((cause: unknown) => {\r\n // A retry (the widget's `Reintentar`: `stop()` then `start()` again) must be a genuine second\r\n // attempt, not a replay of the same cached failure — clearing `pending` is what makes that\r\n // true. Without it, one failed chunk load would fail every future retry forever.\r\n pending = null;\r\n throw cause;\r\n });\r\n return pending;\r\n }\r\n\r\n return {\r\n get state(): SoftphoneState {\r\n return real?.state ?? 'stopped';\r\n },\r\n async start(): Promise<void> {\r\n await (await load()).start();\r\n },\r\n async stop(): Promise<void> {\r\n // Never successfully started (or a failed load left `real` unset) — nothing to stop, and\r\n // calling `load()` here would wrongly turn a no-op into a first attempt.\r\n if (real) await real.stop();\r\n },\r\n async placeCall(destination: string, options?: CallOptions): Promise<EngineCall> {\r\n return (await load()).placeCall(destination, options);\r\n },\r\n onIncoming: (listener) => incoming.subscribe(listener),\r\n onStateChange: (listener) => stateChange.subscribe(listener),\r\n onError: (listener) => error.subscribe(listener),\r\n };\r\n}\r\n","import { SoftphoneImpl } from './softphone-impl.js';\r\nimport { defaultZadarmaEngine } from './default-provider.js';\r\nimport type { CallEngine } from './engine.js';\r\nimport type { Softphone } from './softphone.js';\r\nimport type { SoftphoneConfig } from './types.js';\r\n\r\n/**\r\n * Construct a softphone.\r\n *\r\n * A factory, not `new Softphone()`, for two reasons: it resolves the underlying call engine from\r\n * config without exposing that machinery, and it returns the `Softphone` INTERFACE, so the concrete\r\n * class stays an implementation detail free to change without a major version.\r\n *\r\n * @example\r\n * const phone = createSoftphone({\r\n * tokenSource: TokenSource.endpoint('/voice/token'),\r\n * resolveContact: (n) => api.lookup(n),\r\n * });\r\n * await phone.start(); // from a user gesture — see Softphone.start()\r\n */\r\nexport function createSoftphone(config: SoftphoneConfig): Softphone {\r\n return new SoftphoneImpl(resolveEngine(config), config);\r\n}\r\n\r\n/**\r\n * Pick the call engine.\r\n *\r\n * Three outcomes, in order:\r\n * 1. A valid `config.provider` — used as-is. `apiBase` is never even looked at: an explicit engine\r\n * always wins, so a host who wants a `MockProvider` or their own `organizationId` gets exactly\r\n * that, with no default quietly built alongside it.\r\n * 2. `provider` was SET but is not a valid engine — thrown on immediately, distinctly from \"none\r\n * configured\" below. Silently falling through to the default here would turn a typo'd config\r\n * into a confusing \"why is this calling Zadarma, I passed something else\" bug report.\r\n * 3. `provider` omitted, `apiBase` present — `defaultZadarmaEngine` (SPEC §5.4). It resolves\r\n * synchronously and touches nothing browser-specific until `start()`/`placeCall()` actually run,\r\n * so this stays as SSR-safe as the explicit-provider path.\r\n *\r\n * Neither present: a plain, explicit throw beats a confusing failure deep inside `start()`.\r\n */\r\nfunction resolveEngine(config: SoftphoneConfig): CallEngine {\r\n if (config.provider !== undefined) {\r\n if (isCallEngine(config.provider)) return config.provider;\r\n throw new Error(\r\n '`config.provider` was set but is not a valid CallEngine — it needs at least `placeCall` and ' +\r\n '`onIncoming`. Pass `createZadarmaEngine(...)` from \"@coventia/softphone/zadarma\", a ' +\r\n '`MockProvider` from \"@coventia/softphone/testing\", or omit it entirely to use the default.',\r\n );\r\n }\r\n\r\n if (config.apiBase) {\r\n return defaultZadarmaEngine({\r\n apiBase: config.apiBase,\r\n tokenSource: config.tokenSource,\r\n organizationId: config.coventia?.organizationId,\r\n });\r\n }\r\n\r\n throw new Error(\r\n 'No call engine configured. Pass `config.provider` explicitly — `createZadarmaEngine({ apiBase, ' +\r\n 'tokenSource })` from \"@coventia/softphone/zadarma\", or a `MockProvider` from ' +\r\n '\"@coventia/softphone/testing\" — or set `config.apiBase` so a default Zadarma engine can be built.',\r\n );\r\n}\r\n\r\nfunction isCallEngine(value: unknown): value is CallEngine {\r\n return (\r\n typeof value === 'object' &&\r\n value !== null &&\r\n typeof (value as CallEngine).placeCall === 'function' &&\r\n typeof (value as CallEngine).onIncoming === 'function'\r\n );\r\n}\r\n","/**\r\n * @coventia/softphone — the framework-free engine.\r\n *\r\n * Importable from vanilla JS, Vue, Svelte, a PHP-rendered page, and a Node/SSR process. Exports NO\r\n * React and touches NO browser global at module scope (SPEC §10.1, §10.2). Those two rules — not the\r\n * authoring language, not the package layout — are what make this usable anywhere.\r\n *\r\n * THIS FILE IS THE PUBLIC API. Anything not exported here is free to change without a major version;\r\n * anything exported here is not. Adding to it is cheap and removing from it is expensive, so add\r\n * deliberately.\r\n *\r\n * Notably NOT exported, and deliberately:\r\n * - `SipProvider` and everything under transports/ — the provider seam is internal (SPEC §5.4).\r\n * Exporting it would freeze the shape of a Zadarma migration we have not designed yet.\r\n * - `MockProvider` — lives at @coventia/softphone/testing, so it never lands in a production bundle.\r\n */\r\n\r\n// ── Engine ────────────────────────────────────────────────────────────────────\r\nexport { createSoftphone } from './core/create.js';\r\nexport type { Softphone, AudioDevices } from './core/softphone.js';\r\n\r\n// ── Calls ─────────────────────────────────────────────────────────────────────\r\nexport type { Call, CallState, HangupReason, HoldOriginator } from './core/call.js';\r\nexport { CALL_TRANSITIONS, INITIAL_STATES, canTransition, isTerminal } from './core/call.js';\r\n\r\n// ── Events ────────────────────────────────────────────────────────────────────\r\nexport { SoftphoneEvent, CallEvent } from './core/events.js';\r\nexport type { SoftphoneEventMap, CallEventMap, TypedEmitter } from './core/events.js';\r\n\r\n// ── Errors ────────────────────────────────────────────────────────────────────\r\n// `isFatal` is public because a host has to branch on it — `err.fatal` narrows structurally, but a\r\n// guard is friendlier from JavaScript. `createError` stays internal: the SDK raises these, hosts read\r\n// them, and a host minting our error codes would be claiming a failure we never observed.\r\nexport { ERROR_CATALOGUE, isFatal } from './core/errors.js';\r\nexport type {\r\n SoftphoneError,\r\n SoftphoneErrorCode,\r\n FatalSoftphoneError,\r\n FatalErrorCode,\r\n NonFatalSoftphoneError,\r\n NonFatalErrorCode,\r\n} from './core/errors.js';\r\n\r\n// ── Config & types ────────────────────────────────────────────────────────────\r\nexport type {\r\n SoftphoneConfig,\r\n SoftphoneState,\r\n CallDirection,\r\n CallOptions,\r\n Contact,\r\n RemoteParty,\r\n LogLevel,\r\n TokenSource,\r\n TokenEndpointOptions,\r\n VoiceToken,\r\n RingtoneOption,\r\n RingtoneSettings,\r\n RingtoneSource,\r\n} from './core/types.js';\r\n\r\n// ── Capability probes ─────────────────────────────────────────────────────────\r\nexport { isBrowser, isWebRtcSupported } from './core/env.js';\r\n\r\nexport const VERSION = '0.3.0';\r\n"]}
@@ -128,7 +128,9 @@ function useDevices() {
128
128
  outputs: audio.outputs,
129
129
  currentInputId: audio.currentInputId,
130
130
  currentOutputId: audio.currentOutputId,
131
+ currentRingtoneOutputId: audio.currentRingtoneOutputId,
131
132
  canSelectOutput: audio.canSelectOutput,
133
+ canSelectRingtoneOutput: audio.canSelectRingtoneOutput,
132
134
  setInput: (deviceId) => audio.setInput(deviceId),
133
135
  setOutput: (deviceId) => audio.setOutput(deviceId),
134
136
  setRingtoneOutput: (deviceId) => audio.setRingtoneOutput(deviceId),