bridgething
Surfaces ↓

Phone

client.phone

Daemon -> webapp telephony surface. CallStarted/CallUpdated/ CallEnded/CommunicationsChanged are unsolicited broadcasts driven by the connected companion's telephony state; StateReply/ErrorReply answer webapp-issued requests and 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>

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>

await client.phone.swap();

merge(): Promise<void>

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

Types

shapes referenced above, as the SDK types them

One telephony call. call_id is companion-stable for the call's lifetime; webapps pass it back to answer/decline/end/hold. remote_id is the raw E.164 (or platform raw); display_name is the gateway's resolved contact name when available.

type PhoneCall = {
  callId: string;
  remoteId: string;
  displayName: string;
  status: PhoneCallStatus;
  direction: PhoneCallDirection;
  startedAtUnixS?: number;
  label?: string;
  addressBookId?: string;
  service?: PhoneCallService;
  isConferenced?: boolean;
  conferenceGroup?: number;
};
type PhoneCallEnded = {
  callId: string;
  reason: CallEndReason;
};

Broadcast whenever CommunicationsState changes: signal, registration, or which call-control verbs are currently legal.

type PhoneCommunicationsReply = {
  state: CommunicationsState;
};

Typed reply payload for PhoneStateGet and the unsolicited announce-time snapshot the companion proactively pushes per the announce-on-connect rule.

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';

Call bearer / service kind. iAP2's CallStateUpdateService enum values, projected to our wire surface. Companion gateways that don't distinguish bearers project all calls to Telephony.

type PhoneCallService = 'unknown' | 'telephony' | 'faceTimeAudio' | 'faceTimeVideo';

Why a call ended, surfaced on onPhoneCallEnded. Failed carries a platform-defined reason (network, busy, etc.).

type CallEndReason = 'local' | 'remote' | 'missed' | 'declined' | 'failed';

What call-control verbs are currently legal. Webapps must gate UI on these flags; sending an unavailable verb is a protocol violation, not a no-op. All None = no signal received yet, treat as conservatively 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;
};

Snapshot of every active call known to the gateway. Multi-call is possible (call-waiting, conference) - webapps rendering only one active call typically pick the first non-Held entry.

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

Direction the accessory wants iOS to take when answering an incoming call while another call is active.

type AcceptCallAction =
  | 'accept' // Answer the new call (placing any existing active call on hold if telephony allows it).
  | 'endAndAccept' // End the existing active call and answer the new one.;

Direction the accessory wants iOS to take when ending a call.

type EndCallAction =
  | 'end' // End / decline the call referenced by `CallUUID`.
  | 'endAll' // End every active call.;

What kind of outbound call the accessory wants placed.

type InitiateCallType = 'destination' | 'voicemail' | 'redial';

DTMF tones the accessory can play during an active call.

type DtmfTone =
  | 'd0'
  | 'd1'
  | 'd2'
  | 'd3'
  | 'd4'
  | 'd5'
  | 'd6'
  | 'd7'
  | 'd8'
  | 'd9'
  | 'star'
  | 'hash';

Cellular registration state - populated from iAP2 CommunicationsUpdate or the companion's equivalent.

type RegistrationStatus =
  | 'unknown'
  | 'notRegistered'
  | 'searching'
  | 'denied'
  | 'registeredHome'
  | 'registeredRoaming'
  | 'emergencyCallsOnly';