Voice SDKs

Voice SDK for iOS

sent-dm-voice-swift lets an iOS app place Sent Voice calls: to a phone number, to another user of your app, or into a conference. Your backend mints a short-lived voice token for each user with the Sent API. The SDK registers the device with it, places calls, and reports how they go. What rings when a user calls is decided by your callback, as for every call; see Answering Calls.

This SDK places calls. Receiving calls on a phone needs push credentials registered with Sent, and arrives as a minor version of this SDK with no change to its shape.

Installation

A Swift package for iOS 15 or later. In Xcode, choose File → Add Package Dependencies and enter the repository URL, or add it to Package.swift:

dependencies: [
  .package(url: "https://github.com/sentdm/sent-dm-voice-swift.git", from: "0.1.0"),
],

Add the SentVoice product to your app target. Calls use the microphone, so add NSMicrophoneUsageDescription to the app's Info.plist; iOS asks the user on the first call.

Quick Start

Mint voice tokens on your backend

Mint tokens on your server with your Sent API key, never in the app, for the user who is signed in:

// On your server, behind a route such as GET /api/voice-token
const response = await fetch('https://api.sent.dm/v3/voice/tokens', {
  method: 'POST',
  headers: { 'x-api-key': process.env.SENT_API_KEY, 'Content-Type': 'application/json' },
  body: JSON.stringify({ identity: user.id }),
});
const { data } = await response.json();
return data.token;

identity names the user in calls: letters, digits, - and _, up to 200 characters. The user is bound to your default voice number unless you pass number, one of the numbers you turned voice on for with POST /v3/channels/voice, and the token lasts ttl seconds (600 by default, at most 3600). Return the token as text from an endpoint of your own, such as /api/voice-token. The token is opaque: read the identity, number, and expiry from the minting response, not from the token. The token endpoint and the binding rules are covered in Calls From Your App.

Register and call

import SentVoice

let client = SentVoice.client(options: SentVoiceOptions(tokenProvider: {
  let (data, _) = try await URLSession.shared.data(from: URL(string: "https://example.com/api/voice-token")!)
  return String(decoding: data, as: UTF8.self)
}))

try await client.register()

let call = try await client.connect(to: "+14155551234")
let listener = call.on { event in
  switch event {
  case .connected: print("Connected")
  case .disconnected(let state, _): print("The call ended as \(state)")
  default: break
  }
}

Keep one client for the life of the app. register() fetches a token through tokenProvider, registers with it, and refreshes it before it expires. A listener stays until you call cancel() on the token on returns.

Client Options

SentVoiceOptions takes the following options. Only tokenProvider is required.

OptionDescription
tokenProviderAn async closure that returns a voice token from your backend, which mints it with POST /v3/voice/tokens. Called by register() and again before each token expires
registerRetriesHow many more times registering or refreshing the token is retried after a temporary failure, such as a network error. Default 3
baseURLThe Sent API origin the token exchange and telemetry use. Default https://api.sent.dm
telemetryDisabledTurns off the call quality and error reports the SDK sends to Sent. Default false
logA closure that receives the SDK's log lines with their level. Voice tokens never appear in them

Client

The client holds one registration and the call that runs on it.

PropertyDescription
stateunregistered, registering, registered, offline or destroyed. After destroy() the state is destroyed and every method throws
identityThe identity the current token was minted for. nil until registered
numberThe number the identity is bound to. nil until registered
activeCallThe call in progress, or nil
MethodDescription
register()Fetches a token from tokenProvider, registers with it, and keeps it refreshed. Returns once the client is registered
unregister()Drops the registration and leaves a call in progress alone
destroy()Ends the call and tears the client down for good
connect(to:)Calls a phone number in E.164 format or another user of your app by identity. See Calls
joinConference(name:)Joins one of your account's rooms by name
on(_:)Adds a listener for the events below and returns a token; cancel() on it removes the listener
EventFires
registeringregister() started
registeredThe client is registered and ready for calls
unregisteredunregister() finished
activeCallChanged(Call?)A call was placed, or the call in progress ended
tokenWillExpire(expiresAt:)The token is about to expire and the next refresh is due
offline(SentVoiceError)The refresh retries ran out. The client tries again about every 30 seconds; register() tries at once
error(SentVoiceError)Something failed outside a method call, such as a refresh

Calls

connect(to:) and joinConference(name:) return a Call once the provider accepted it; the call then moves through ringing, answered and connected to completed, failed, busy or no_answer. One call at a time: a second connect while one is in progress throws CALL_IN_PROGRESS.

PropertyDescription
id, direction, from, to, startedAtWhat the call is
stateThe call's state, as above
isMutedWhether the microphone is muted
connectedTime, endedTimeWhen the parties were joined, and when the call ended
errorThe error that ended the call, when one did
MethodDescription
hangup()Ends the call
mute(_:)Mutes or unmutes the microphone
sendDigits(_:)Sends DTMF digits, for example to a phone menu
on(_:)Adds a listener for the events below and returns a token
EventFires
ringing, answered, connectedThe call progressed
reconnecting, reconnectedThe media path dropped and came back
disconnected(state:error:)The call ended in state, with the error that ended it when there is one
muteChanged(Bool)mute(_:) took effect
qualityWarning(QualityWarning)The media quality degraded
error(SentVoiceError)Something failed on the call

Errors

Every failure is a SentVoiceError with a code, a category, whether it is retriable, and a message you can show. The codes are the same on every platform:

CodeMeaning
TOKEN_INVALIDThe value from tokenProvider is not a voice token, or Sent refused it
TOKEN_EXPIREDThe token expired and could not be refreshed
NOT_REGISTEREDA call was placed while the client was not registered
MEDIA_PERMISSION_DENIEDThe user refused the microphone
INVALID_ADDRESSto is neither an E.164 number nor an identity, or a room name is malformed
CALL_IN_PROGRESSA call was placed while one was in progress
CALL_REJECTEDThe other party, or your callback, declined the call
CALL_FAILEDThe call could not be placed or dropped
NETWORKSent or the provider could not be reached. Retriable
CAPABILITY_UNSUPPORTEDThe device or the SDK version cannot do what was asked
UNKNOWNAnything else; message says what

Telemetry

The SDK reports call quality and errors to Sent so support can see what a user saw, batched and sent in the background. Set telemetryDisabled to turn it off. Nothing in the reports identifies the person beyond the identity your backend minted the token for.

On this page