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);
} type Result =
| { ok: true; response: PhoneStateReply }
| { ok: false; kind: 'domain'; error: never }
| { ok: false; kind: 'protocol'; error: WireError }; 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';