index ↓

phone

client.phone

Call state from the connected phone. onCallStarted, onCallUpdated, and onCallEnded track each call, and onCommunicationsChanged reports signal, registration, and usable commands.

requests

you ask, the daemon answers. await the tagged result and check .ok

stateGet(): Promise<TypedRequestResult<PhoneStateReply, never>>

push form: onStateReply (subscribe instead of awaiting)

const res = await client.phone.stateGet();
if (res.ok) {
  console.log(res.response.state);
}

commands

fire-and-forget. the promise resolves once the daemon has taken the message

answer(PhoneCallAction): Promise<void>

await client.phone.answer({ callId: '...' });

accept(PhoneAcceptAction): Promise<void>

await client.phone.accept({ callId: '...', action: 'accept' });

decline(PhoneCallAction): Promise<void>

await client.phone.decline({ callId: '...' });

end(PhoneCallAction): Promise<void>

await client.phone.end({ callId: '...' });

endTyped(PhoneEndAction): Promise<void>

await client.phone.endTyped({ callId: '...', action: 'end' });

hold(PhoneCallAction): Promise<void>

Requires holdAvailable on CommunicationsState.

await client.phone.hold({ callId: '...' });

unhold(PhoneCallAction): Promise<void>

await client.phone.unhold({ callId: '...' });

initiate(PhoneInitiateAction): Promise<void>

await client.phone.initiate({ kind: 'destination' });

swap(): Promise<void>

Requires swapAvailable on CommunicationsState.

await client.phone.swap();

merge(): Promise<void>

Requires mergeAvailable on CommunicationsState.

await client.phone.merge();

mute(PhoneMuteAction): Promise<void>

await client.phone.mute({ mute: true });

dtmf(PhoneDtmfAction): Promise<void>

await client.phone.dtmf({ tone: 'd0' });

events

the daemon pushes these unprompted. subscribing returns an unsubscribe function

onCallStarted(handler: (PhoneCall) => void): () => void

const off = client.phone.onCallStarted((phoneCall) => {
  console.log(phoneCall.callId);
});
// call off() to unsubscribe

onCallUpdated(handler: (PhoneCall) => void): () => void

const off = client.phone.onCallUpdated((phoneCall) => {
  console.log(phoneCall.callId);
});
// call off() to unsubscribe

onCallEnded(handler: (PhoneCallEnded) => void): () => void

const off = client.phone.onCallEnded((phoneCallEnded) => {
  console.log(phoneCallEnded.callId);
});
// call off() to unsubscribe

onCommunicationsChanged(handler: (PhoneCommunicationsReply) => void): () => void

const off = client.phone.onCommunicationsChanged((reply) => {
  console.log(reply.state);
});
// call off() to unsubscribe

onErrorEvent(handler: (PhoneErrorReply) => void): () => void

const off = client.phone.onErrorEvent((reply) => {
  console.log(reply.error);
});
// call off() to unsubscribe

types

shapes referenced above, as the sdk types them

callId stays the same for the life of the call. Pass it to answer, decline, end, hold.

type PhoneCall = {
  callId: string;
  remoteId: string; // E.164 form when the phone provides one.
  displayName: string;
  status: PhoneCallStatus;
  direction: PhoneCallDirection;
  startedAtUnixS?: number;
  label?: string;
  addressBookId?: string;
  service?: PhoneCallService;
  isConferenced?: boolean;
  conferenceGroup?: number;
};
type PhoneCallEnded = {
  callId: string;
  reason: CallEndReason;
};
type PhoneCommunicationsReply = {
  state: CommunicationsState;
};
type PhoneErrorReply = {
  error: PhoneError;
};
type PhoneStateReply = {
  state: PhoneState;
};
type PhoneCallAction = {
  callId: string;
};
type PhoneAcceptAction = {
  callId: string;
  action: AcceptCallAction;
};
type PhoneEndAction = {
  callId: string;
  action: EndCallAction;
};
type PhoneInitiateAction = {
  kind: InitiateCallType;
  destinationId?: string;
  service?: PhoneCallService;
  addressBookId?: string;
};
type PhoneMuteAction = {
  mute: boolean;
};
type PhoneDtmfAction = {
  callId?: string;
  tone: DtmfTone;
};
type PhoneCallStatus =
  | 'disconnected'
  | 'sending'
  | 'ringing'
  | 'connecting'
  | 'active'
  | 'held'
  | 'disconnecting';
type PhoneCallDirection = 'incoming' | 'outgoing';

How the call is carried. A phone that does not distinguish bearers reports telephony.

type PhoneCallService = 'unknown' | 'telephony' | 'faceTimeAudio' | 'faceTimeVideo';
type CallEndReason = 'local' | 'remote' | 'missed' | 'declined' | 'failed';

Enable a call-control button only while its *Available flag is true. Treat a null flag as unavailable.

type CommunicationsState = {
  signalStrength?: number;
  registrationStatus?: RegistrationStatus;
  airplaneMode?: boolean;
  carrierName?: string;
  cellularSupported?: boolean;
  telephonyEnabled?: boolean;
  faceTimeAudioEnabled?: boolean;
  faceTimeVideoEnabled?: boolean;
  muteStatus?: boolean;
  currentCallCount?: number;
  newVoicemailCount?: number;
  initiateCallAvailable?: boolean;
  endAndAcceptAvailable?: boolean;
  holdAndAcceptAvailable?: boolean;
  swapAvailable?: boolean;
  mergeAvailable?: boolean;
  holdAvailable?: boolean;
};
type PhoneError =
  | 'callNotFound' // The phone reports no call with this id.
  | 'actionRejected'
  | 'noTarget' // No phone is connected.
  | 'unavailable' // The verb's `*Available` flag was false when the action ran.;

Call waiting and conference calls produce more than one entry.

type PhoneState = {
  activeCalls: PhoneCall[];
};

What to do with an existing call when answering a new one.

type AcceptCallAction =
  | 'accept' // Answer the new call and hold the existing one.
  | 'endAndAccept' // End the existing call and answer the new one.;
type EndCallAction = 'end' | 'endAll';
type InitiateCallType = 'destination' | 'voicemail' | 'redial';
type DtmfTone =
  | 'd0'
  | 'd1'
  | 'd2'
  | 'd3'
  | 'd4'
  | 'd5'
  | 'd6'
  | 'd7'
  | 'd8'
  | 'd9'
  | 'star'
  | 'hash';
type RegistrationStatus =
  | 'unknown'
  | 'notRegistered'
  | 'searching'
  | 'denied'
  | 'registeredHome'
  | 'registeredRoaming'
  | 'emergencyCallsOnly';