@audin.ai/operator-sdk
Version:
Headless browser SDK for the Audin operator softphone — make and receive calls over the Audin operator WebSockets.
92 lines (91 loc) • 3.75 kB
TypeScript
/**
* PresenceClient — the signalling channel.
*
* A single WebSocket to the Audin presence endpoint over which the operator:
* - announces availability on a set of phone numbers (`set_available`),
* - keeps the connection alive (`ping` heartbeat),
* - answers / declines inbound offers (`accept` / `reject`),
* - starts outbound calls (`start_outbound`).
*
* and over which the server pushes: `connected`, `available_set`,
* `incoming_call`, `call_taken`, `call_assigned`, `outbound_started`, `pong`,
* `error`.
*
* Resilience built in here so the higher-level `AudinOperator` doesn't have to:
* - HEARTBEAT: a `ping` every `heartbeatIntervalMs`; the server reaps idle
* connections at ~90s.
* - RECONNECT: on unexpected close, walk a backoff schedule, fetch a FRESH
* token via `getToken` (the old one may have expired), reconnect, and
* re-send the last `set_available` so the operator comes back online
* automatically.
*
* This class is transport-only: it parses/dispatches messages and owns the
* socket lifecycle. It holds NO call state and names NO providers.
*/
import type { OperatorLogger } from "./types.js";
/** A server→client message. Loosely typed; the handler narrows on `type`. */
export interface PresenceServerMessage {
type: string;
[key: string]: unknown;
}
export interface PresenceClientOptions {
/** Base WS URL WITHOUT query, e.g. `wss://host/operator-presence/ws`. */
presenceWsUrl: string;
/**
* Resolve a valid session token (cached & shared with REST/audio). Returns
* the raw JWT string. Should re-mint internally once expired.
*/
ensureToken: () => Promise<string>;
/**
* Drop the cached token so the next {@link ensureToken} re-mints. Invoked on
* an auth-related socket close so a reconnect uses a fresh token.
*/
invalidateToken: () => void;
heartbeatIntervalMs: number;
reconnectBackoffMs: number[];
logger: OperatorLogger;
/** Invoked for every parsed server message. */
onMessage: (msg: PresenceServerMessage) => void;
/** Connection lifecycle for the higher layer (state, errors). */
onOpen: () => void;
onReconnecting: () => void;
onError: (code: string, message: string, cause?: unknown) => void;
}
export declare class PresenceClient {
private readonly opts;
private ws;
private heartbeatTimer;
private reconnectTimer;
private reconnectAttempt;
/** True between `connect()` and `disconnect()` — gates auto-reconnect. */
private desiredOnline;
/** Last availability we announced, replayed after a reconnect. */
private lastAvailability;
constructor(opts: PresenceClientOptions);
/** Open the presence socket and keep it open (auto-reconnecting). */
connect(): Promise<void>;
/**
* Close the socket and stop reconnecting. Best-effort `set_unavailable`
* first so the server drops presence immediately rather than waiting for the
* reaper.
*/
disconnect(): void;
/** Announce availability on `phoneNumberIds`; remembered for reconnects. */
setAvailable(phoneNumberIds: string[]): void;
/** Drop availability (stays connected). */
setUnavailable(): void;
/** Answer an inbound offer. */
accept(callSid: string): void;
/** Decline an inbound offer. */
reject(callSid: string): void;
/** Request an outbound call. The server replies with `outbound_started`. */
startOutbound(to: string, callerId: string): void;
/** Whether the socket is currently open. */
get isOpen(): boolean;
private openOnce;
private scheduleReconnect;
private startHeartbeat;
private stopHeartbeat;
private clearTimers;
private sendRaw;
}