@audin.ai/operator-sdk
Version:
Headless browser SDK for the Audin operator softphone — make and receive calls over the Audin operator WebSockets.
168 lines (167 loc) • 6.99 kB
TypeScript
/**
* Public + protocol types for `@audin.ai/operator-sdk`.
*
* Naming is deliberately neutral: this SDK is shipped to partners and never
* names the underlying telephony or AI providers. It speaks only of "calls",
* "phone numbers" and "audio".
*/
/**
* The token your application's backend returns for a softphone session.
*
* Your backend calls the Audin REST API `POST /operator-sessions/token`
* (authenticated with your Account API Key — which NEVER leaves your server)
* and returns at least the `token` here. The SDK presents this token to the
* Audin WebSockets; it is short-lived (~1h) and the SDK requests a fresh one
* via {@link AudinOperatorConfig.getToken} whenever it (re)connects.
*/
export interface OperatorSessionToken {
/** Short-lived session JWT the SDK presents to the Audin WebSockets. */
token: string;
/** Optional ISO-8601 expiry. If provided, the SDK refreshes ahead of it. */
expiresAt?: string;
}
/** Async provider of a fresh {@link OperatorSessionToken}. */
export type GetTokenFn = () => Promise<OperatorSessionToken>;
/**
* A phone number the operator's account owns and may go online on / dial from.
* Returned by {@link AudinOperator.listPhoneNumbers}.
*/
export interface OperatorPhoneNumber {
/** Stable identifier — pass to `goOnline([...])`. */
id: string;
/** E.164 number — present as `callerId` when dialling. */
phoneNumber: string;
/** Human label, when set. */
displayName: string | null;
}
/** Direction of a call from the operator's point of view. */
export type CallDirection = "inbound" | "outbound";
/** Lifecycle state of a {@link OperatorCall}. */
export type CallState = "ringing" | "connecting" | "active" | "ended";
/** Why a call ended — surfaced on the `callEnded` event. */
export type CallEndReason = "hangup" | "remote_hangup" | "taken_by_other" | "rejected" | "no_answer" | "failed" | "offline";
/**
* Configuration for {@link AudinOperator}.
*/
export interface AudinOperatorConfig {
/**
* Base URL of the Audin operator service, e.g. `https://core.<audin-host>`.
* `http(s)` is accepted and upgraded to the matching `ws(s)` scheme
* internally. Trailing slashes are tolerated.
*/
coreUrl: string;
/**
* Returns a fresh session token. Called on initial connect AND on every
* reconnect, so always fetch a NEW token (do not cache an expired one).
* This is the single seam through which credentials enter the SDK — the
* Account API Key stays on your backend.
*/
getToken: GetTokenFn;
/**
* Heartbeat interval (ms) for the presence connection. Defaults to 25000.
* The server reaps connections idle for ~90s, so keep this well under that.
*/
heartbeatIntervalMs?: number;
/**
* Reconnect backoff schedule (ms) for the presence connection. The SDK
* walks this array on consecutive failures and stays on the last value
* thereafter. Defaults to `[1000, 2000, 5000, 10000, 30000]`.
*/
reconnectBackoffMs?: number[];
/**
* Constraints passed to `getUserMedia({ audio })` for microphone capture.
* Defaults enable echo cancellation, noise suppression and auto gain.
*/
audioConstraints?: MediaTrackConstraints;
/** Optional sink for SDK diagnostics. Defaults to `console`. */
logger?: OperatorLogger;
}
/** Minimal logger surface the SDK writes to (defaults to `console`). */
export interface OperatorLogger {
debug(...args: unknown[]): void;
info(...args: unknown[]): void;
warn(...args: unknown[]): void;
error(...args: unknown[]): void;
}
/** Connection state of the presence channel. */
export type PresenceState = "offline" | "connecting" | "online" | "reconnecting";
/**
* Events emitted by {@link AudinOperator}. Subscribe with `op.on(name, cb)`.
*/
export interface AudinOperatorEventMap {
/** Presence channel state changed. */
presenceStateChanged: PresenceState;
/** The set of phone numbers the operator is online on was (re)confirmed. */
availabilityChanged: {
accepted: string[];
rejected: string[];
};
/** An inbound call is ringing. Call `call.accept()` or `call.reject()`. */
incomingCall: OperatorCall;
/** A call's audio is being established (after accept / dial). */
callStarted: OperatorCall;
/** A call ended. Inspect `call.endReason`. */
callEnded: OperatorCall;
/** A non-fatal error. The SDK keeps running where it can. */
error: OperatorError;
}
/** Names of the events in {@link AudinOperatorEventMap}. */
export type AudinOperatorEventName = keyof AudinOperatorEventMap;
/** Listener signature for a given event. */
export type AudinOperatorListener<K extends AudinOperatorEventName> = (payload: AudinOperatorEventMap[K]) => void;
/** Structured error surfaced on the `error` event. */
export interface OperatorError {
/** Stable machine code, e.g. `MIC_PERMISSION_DENIED`, `WS_ERROR`. */
code: string;
/** Human-readable description. */
message: string;
/** Original error / server payload, if any. */
cause?: unknown;
}
/** Options for {@link AudinOperator.dial}. */
export interface DialOptions {
/**
* Caller ID to present — MUST be a phone number your account owns and that
* is active. Required by the platform for outbound calls.
*/
callerId: string;
}
/**
* Handle to a single call. Obtained from the `incomingCall` / `callStarted`
* events or returned by {@link AudinOperator.dial}.
*/
export interface OperatorCall {
/** Platform call identifier. */
readonly callSid: string;
/** inbound | outbound. */
readonly direction: CallDirection;
/** Remote party number (E.164) when known. */
readonly from?: string;
/** Local/called number (E.164) when known. */
readonly to?: string;
/** Current lifecycle state. */
readonly state: CallState;
/** Populated once `state === "ended"`. */
readonly endReason?: CallEndReason;
/** Answer an inbound offer. No-op if not ringing. */
accept(): void;
/** Decline an inbound offer. No-op if not ringing. */
reject(): void;
/** Mute / unmute the operator microphone toward the far end. */
mute(on: boolean): void;
/** Whether the operator microphone is currently muted. */
readonly muted: boolean;
/**
* Send a DTMF digit (`0-9`, `*`, `#`).
*
* NOT YET SUPPORTED END-TO-END. The digit is validated and emitted as a
* control message on the call channel, but the server does not yet forward
* the tones onto the telephone network — so today this is effectively a
* functional no-op for the far end. The method is kept (and the wire message
* remains valid) so that enabling it server-side in a future version requires
* no SDK change, but do NOT rely on the remote party hearing the tones yet.
*/
sendDtmf(digit: string): void;
/** Hang up the call. */
hangup(): void;
}