Skip to content

TypeScript API ​

Latest package release: 3.0.0

Audio routes, DTMF, caller name updates and system call requests are documented in phone features. They use contract 0.3.0 and are published on npm and pub.dev.

ts
import {Callx} from '@bear-block/callx';

The package ships TypeScript types. Core values are exported from @bear-block/callx; the simulator is in @bear-block/callx/preview, and video/PiP exports are in @bear-block/callx/video.

Callx ​

ts
const callx = new Callx();

Create one instance for the app's lifetime. The constructor takes an optional backend for tests; by default it uses the native module.

Setup and state ​

MethodReturnsDescription
setup(config?: CallxConfig)Promise<Capabilities>Connects to the native core and returns what the runtime supports; takes no required arguments
getSnapshot()Promise<Snapshot>The current call, once
observe(listener)() => voidCalls listener(snapshot) now and on every change; returns an unsubscribe function
getPushToken()Promise<PushToken | null>The device's call push token: {type: 'voip', token} on iOS, {type: 'fcm', token} on Android
dispose()voidReleases this instance. Does not hang up

setup() takes no arguments. The incoming and ongoing call screens show your app's display name: CFBundleDisplayName on iOS (change the CallKit icon and ringtone through providerConfiguration, see iOS) and android:label on Android. CallxConfig.appName is deprecated and ignored; passing it still compiles.

Commands ​

All commands accept an optional last argument options: CommandOptions and resolve with a CommandResult. See commands and results.

MethodDescription
startCall(input: CallInput, options?)Starts an outgoing call
answer(callId, options?)Answers the incoming call; the call becomes connecting
end(callId, options?)Declines, cancels or hangs up
setMuted(callId, muted: boolean, options?)Mutes or unmutes the microphone
setHeld(callId, held: boolean, options?)Holds or resumes the call
setCamera(callId, on: boolean, options?)Turns the local camera on or off; needs a video adapter and the app in front. See video calls
switchCamera(callId, facing: CameraFacing, options?)Chooses the front or back camera; remembered while the camera is off
setAudioRoute(callId, routeId, options?)Select an observed audio endpoint
sendDtmf(callId, digits, options?)Send keypad tones during an active call; requires dtmf
setDisplayName(callId, name, options?)Update the caller name
queryOperation(operationId, accountGeneration)Looks up a stored result: Promise<OperationLookup>

Observation sessions ​

MethodReturnsDescription
openSession(afterSequence?: string)Promise<ObservationSession>Opens the session with a snapshot and replay after the cursor
observeEvents(sessionId, listener)() => voidLive events for the session
acknowledge(sessionId, throughSequence)Promise<void>Marks events as processed
closeSession(sessionId)Promise<void>Closes the session (not the call)

See observation and replay.

Types ​

ts
interface CallxConfig { /** @deprecated Ignored. */ appName?: string }

interface CallInput { callId: string; displayName: string; handle: string; video?: boolean }

interface CommandOptions { operationId?: string; deadlineAtMs?: number }

interface Capabilities {
  contractVersion: '0.3.0';
  coreVersion: string;
  execution: 'native' | 'preview';
  accountGeneration: string;
  nativeCalling: boolean;
  durableReplay: boolean;
  providerManagedSignaling: boolean;
  hold: boolean;
  mute: boolean;
  video: boolean;          // a video media adapter is installed
  dtmf: boolean;           // an optional keypad-tone adapter is installed
}

interface Snapshot { sequence: string; call: Call | null }

interface Call {
  callId: string;
  displayName: string;
  direction: 'incoming' | 'outgoing';
  state: CallState;
  muted: boolean;
  mediaReady: boolean;
  mediaInterrupted?: boolean;
  video?: boolean;              // offered or started as a video call
  localVideo?: LocalVideo;      // absent means 'off'
  cameraFacing?: CameraFacing;  // present while localVideo is not 'off'
  remoteVideo?: boolean;        // a remote video track can be rendered
  audioRoutes?: readonly AudioRoute[];
  audioRoute?: string;           // current observed route id
  endReason?: EndReason;
  createdAtMs?: number;
  acceptedAtMs?: number;
  mediaConnectedAtMs?: number;
  endedAtMs?: number;
}

type AudioRouteKind = 'earpiece' | 'speaker' | 'bluetooth' | 'wired' | 'other';
interface AudioRoute { id: string; kind: AudioRouteKind; name: string }  // id is opaque
interface CallRequest { handle: string; displayName?: string; video: boolean }

type CallState = 'incoming' | 'outgoing' | 'connecting' | 'active' | 'held' | 'ended';

type LocalVideo = 'off' | 'on' | 'blocked';   // blocked: the OS took the camera, e.g. in the background
type CameraFacing = 'front' | 'back';

type EndReason = 'localHangup' | 'declined' | 'remoteEnded' | 'callerCancelled' | 'unanswered'
  | 'busy' | 'failed' | 'answeredElsewhere' | 'declinedElsewhere';

interface CommandResult {
  contractVersion: '0.3.0';
  operationId: string;
  status: 'applied' | 'rejected' | 'timedOut' | 'unknown';
  execution: 'native' | 'preview';
  completedAtMs: number;
  error?: OperationError;
}

interface OperationError {
  code: ErrorCode;
  message: string;
  retryable: boolean;
  platform?: {domain: string; code: string};
}

interface OperationLookup {
  contractVersion: '0.3.0';
  operationId: string;
  accountGeneration: string;
  status: 'available' | 'unavailable' | 'generationMismatch';
  result?: CommandResult;
}

interface ObservationSession {
  contractVersion: '0.3.0';
  sessionId: string;
  accountGeneration: string;
  status: 'fresh' | 'resumed' | 'resynced';
  snapshot: {contractVersion: '0.3.0'; watermark: string; calls: Call[]};
  replay: CallEvent[];
}

interface CallEvent {
  contractVersion: '0.3.0';
  eventId: string;
  sequence: string;
  kind: 'callChanged' | 'operationCompleted' | 'resyncRequired';
  source: 'local' | 'platform' | 'signaling' | 'media' | 'recovery';
  observedAtMs: number;
  callId?: string;
  operationId?: string;
}

interface PushToken { type: 'voip' | 'fcm'; token: string }

Constants ​

ExportValue
CONTRACT_VERSION'0.3.0'
CALL_STATESAll CallState values
END_REASONSAll EndReason values
COMMAND_STATUSESAll command statuses
ERROR_CODESAll error codes

CallxError ​

ts
class CallxError extends Error { readonly code: string }

Thrown for validation errors before the native call. Native rejections may also surface as the framework's own errors; read error.code when present. See errors.

Simulator ​

ts
import {createCallxPreview} from '@bear-block/callx/preview';
const {callx, simulator} = createCallxPreview();
simulator methodSimulates
incoming(input: CallInput)An invitation that rings
remoteAnswered()The other side answered your outgoing call
mediaConnected()Media starts flowing
remoteEnded()The other side hung up
remoteVideo(available: boolean)The other side starts or stops sending video
cameraBlocked(blocked: boolean)The OS takes the camera, or gives it back
reset()Clears all state

CallxVideoView ​

tsx
import {CallxVideoView} from '@bear-block/callx/video';

<CallxVideoView callId={call.callId} source="remote" fit="cover" style={{flex: 1}} />
PropTypeDefaultDescription
callIdstringrequiredThe call whose video to show
source'local' | 'remote''remote'The camera of this device, or the other side's video
fit'cover' | 'contain''cover'Crop to fill the view, or letterbox inside it
mirrorbooleanfalseFlip horizontally, usually for the front camera preview

Plus the usual ViewProps such as style. A Fabric component, with a legacy view manager for the old architecture on iOS. The view stays empty until the source exists. See video calls.

Android picture in picture ​

Import these functions from @bear-block/callx/video:

ExportBehaviour
configurePictureInPicture({automatic: boolean}): voidEnables auto-entry on Android 12+ while an answered video call is live
enterPictureInPicture(): Promise<boolean>Requests entry; false when the activity or device cannot enter PiP
addPictureInPictureListener(listener: (inPiP: boolean) => void): () => voidReports mode changes; returns an unsubscribe function

The same APIs work on iOS 15+ (experimental). Android PiP uses the entire activity; your app renders the video or its own branded fallback. See PiP layout.

@bear-block/callx-livekit ​

ts
import {configureLiveKit, resetLiveKit} from '@bear-block/callx-livekit';
ExportDescription
configureLiveKit(config: LiveKitConfig): Promise<void>Persists where room credentials come from
resetLiveKit(): Promise<void>Forgets it, for example on sign-out
validateConfig(config): voidThrows CallxLiveKitError for an invalid configuration
CallxLiveKitErrorError class
ts
interface LiveKitConfig {
  tokenUrl: string;                          // http(s) URL
  headers?: Record<string, string>;          // stored encrypted
}

Optional call UI ​

Import package:callx/callx_ui.dart in Flutter or @bear-block/callx/ui in React Native. UI observes snapshots and invokes host callbacks; it never creates another native call owner.

Export / optionPurpose / default
CallxCallOverlay, CallxMiniCall, CallxPresentationControllerRoot presentation and in-app minimize/expand
CallxCallScreenSupplied voice/video layout with host controls and media rendering
CallxCallControlSelected/disabled/destructive presentation; default size 58dp, host callback
autoHideControlstrue; connected video hides after idle timeout
compactVideoControlstrue; compact video row
controlsPinnedfalse; pin while showing host dialogs or commands
controlsTimeoutMsFive seconds; foreground return starts a fresh timeout
leadingControls / endControlHost top-left and End slots
header / statusLabel / previewPositionHost header/status and preview placement
CallxCallBrandBackground/accent/foreground/surface/danger colors and logo

RN hosts can supply contentInsets from a safe-area provider, previewStyle, style and labels. CallxControlGlyph is optional; hosts can supply their own icons. renderVideo supplies the media surface.

See Call UI for composition and platform distinctions.

Backend events (3.0.1) ​

reportRemoteAnswered(callId) and reportRemoteEnded(callId, reason = 'remoteEnded'), exported from @bear-block/callx, hand call.accepted and call.ended from your JavaScript signaling client to the native ingress. Both resolve true when the call changed, and reject with notConfigured without the native bootstrap, invalidArgument for an invalid ID or reason, and nativeUnavailable when the installed native module predates them (rebuild the app). See call flows.

System call requests ​

callx.addCallRequestListener(listener) returns an unsubscribe function. The listener receives {handle, displayName?, video} and the host decides whether to place a call. Use one app-level consumer; see system handoff and native setup.

Released under the MIT License. No telemetry, in the library or on this site.