@audin.ai/operator-sdk
Version:
Headless browser SDK for the Audin operator softphone — make and receive calls over the Audin operator WebSockets.
140 lines (139 loc) • 6.68 kB
TypeScript
/**
* AudinOperator — the public entry point of `@audin.ai/operator-sdk`.
*
* A headless (no-UI) controller an operator's browser app drives to make and
* receive phone calls through Audin. It hides:
* - the signalling channel (PresenceClient: availability, ringing, dialing),
* - the media channel (AudioBridge: microphone, μ-law transcoding, playback),
* - reconnection, heartbeats and token refresh.
*
* Credentials never enter the browser: the app supplies a `getToken` callback
* that calls ITS OWN backend (which holds the account key) to mint a
* short-lived session token. The SDK only ever sees that token.
*
* MVP concurrency: ONE active call at a time. While a call is live, an inbound
* offer is auto-declined (`reject`) and `dial()` rejects with an error. This
* keeps the audio graph and state machine simple; multi-line can layer on top
* later without changing the public surface.
*
* Naming is provider-neutral throughout — this is "the Audin operator
* softphone", nothing more.
*/
import { TypedEmitter } from "./emitter.js";
import type { AudinOperatorConfig, AudinOperatorEventMap, AudinOperatorEventName, AudinOperatorListener, DialOptions, OperatorCall, OperatorPhoneNumber, PresenceState } from "./types.js";
export declare class AudinOperator extends TypedEmitter<AudinOperatorEventMap> {
private readonly presence;
private readonly coreWsHost;
private readonly coreHttpBase;
private readonly tokens;
/**
* Microphone constraints handed to the next {@link AudioBridge} when a call's
* audio leg opens. Mutable so {@link setAudioConstraints} can switch the input
* device at runtime; read fresh in {@link openAudioBridge} (never captured
* earlier), so the change applies from the next call.
*/
private audioConstraints;
private readonly logger;
private presenceState;
/** The single active/ringing call (MVP: one at a time). */
private activeCall;
private activeBridge;
/** Pending outbound dial awaiting its `outbound_started` ack. */
private pendingOutbound;
constructor(config: AudinOperatorConfig);
on<K extends AudinOperatorEventName>(event: K, listener: AudinOperatorListener<K>): () => void;
off<K extends AudinOperatorEventName>(event: K, listener: AudinOperatorListener<K>): void;
once<K extends AudinOperatorEventName>(event: K, listener: AudinOperatorListener<K>): () => void;
/**
* List the phone numbers this account owns and may go online on / dial from.
*
* Fetches over REST using the SAME short-lived session token the WebSockets
* use (obtained through `getToken`) — the Account API Key never enters the
* browser. On a 401 the cached token is invalidated and the request is
* retried ONCE with a fresh token; a second 401 throws an
* {@link OperatorRequestError} with code `UNAUTHORIZED`. Other non-OK
* responses throw with code `REQUEST_FAILED`.
*
* Use a returned number's `id` for {@link goOnline} and its `phoneNumber`
* (E.164) as the `callerId` for {@link dial}.
*/
listPhoneNumbers(): Promise<OperatorPhoneNumber[]>;
private fetchPhoneNumbers;
/**
* Go online and start accepting inbound calls on `phoneNumberIds`. Connects
* the presence channel (fetching a token via `getToken`) and announces
* availability. Safe to call again to change the number set.
*/
goOnline(phoneNumberIds: string[]): Promise<void>;
/**
* Go offline: drop availability, tear down any active call, and close the
* presence channel (no auto-reconnect until `goOnline` again).
*/
goOffline(): Promise<void>;
/** Current presence channel state. */
get state(): PresenceState;
/** The current active/ringing call, if any. */
get currentCall(): OperatorCall | null;
/**
* Switch the microphone constraints used to capture the operator's audio —
* typically to pick a specific input device, e.g.
* `setAudioConstraints({ deviceId: { exact: id }, echoCancellation: true })`.
*
* Takes effect from the NEXT call: a call already in progress keeps the
* device its {@link AudioBridge} opened with (this does not re-open the
* microphone mid-call). The new constraints are read when the next
* `dial`/accepted-inbound opens its audio leg.
*/
setAudioConstraints(constraints: MediaTrackConstraints): void;
/**
* Place an outbound call to `to` (E.164), presenting `options.callerId`
* (which must be an active number your account owns). Resolves once the call
* is accepted by the platform and the audio bridge is opening; rejects on
* validation failure, a busy operator, or if the platform doesn't ack in
* time.
*/
dial(to: string, options: DialOptions): Promise<OperatorCall>;
private handlePresenceMessage;
private handleIncomingCall;
private handleCallTaken;
private handleCallAssigned;
private handleOutboundStarted;
/**
* Explicit end-of-call notification from the platform (presence channel).
*
* Authoritative: the platform sends `call_ended` when the call terminates
* for ANY reason — remote hangup, no answer, busy, failure — including the
* cases where the audio leg never opened at all (e.g. an outbound the far
* end never answered), so the audio-WS close alone could never signal them.
*/
private handleCallEnded;
private openAudioBridge;
private onBridgeClosed;
private makeCall;
private endCallWithReason;
private teardownActiveCall;
private failPendingOutbound;
private setPresenceState;
private emitError;
/** Public-facing emit wrapper (the base `emit` is protected). */
private emitEvent;
}
/**
* Normalise a base URL into a `ws(s)://host` origin with no trailing slash and
* no path. Accepts `http(s)` or `ws(s)`; upgrades the scheme accordingly.
*/
export declare function toWsBase(coreUrl: string): string;
/**
* Normalise a base URL into an `http(s)://host` origin with no trailing slash
* and no path. Accepts `http(s)` or `ws(s)`; downgrades the WS scheme to the
* matching HTTP scheme for REST calls.
*/
export declare function toHttpBase(coreUrl: string): string;
/** Error thrown by the SDK's REST helpers (e.g. {@link AudinOperator.listPhoneNumbers}). */
export declare class OperatorRequestError extends Error {
/** Stable machine code: `UNAUTHORIZED` | `REQUEST_FAILED`. */
readonly code: string;
/** Original error / response context, if any. */
readonly cause?: unknown;
constructor(code: string, message: string, cause?: unknown);
}