Boostlingo iOS
The Boostlingo iOS Swift library enables developers to embed the Boostlingo caller directly into their own applications. This can then be used for placing calls in the Boostlingo platform.
Getting Started
In order to place calls in Boostlingo, you must have a requestor account. You can then embed Boostlingo iOS into your application, request a Boostlingo API token from your server, and start making calls.
Installation
CocoaPods
It’s easy to install the framework if you manage your dependencies using CocoaPods. Simply add the following to your Podfile:
source 'https://github.com/cocoapods/specs'
target 'TARGET_NAME' do
use_frameworks!
pod 'BoostlingoSDK', '1.0.3'
end
Then run pod install --verbose
to install the dependencies to your project.
Usage
These steps will guide you through the basic process of placing calls through Boostlingo.
Request Boostlingo authentication token
First step is to obtain a boostlingo authentication token from your server. Never store an API token or username/password in your front end/mobile code. Your server should be the one that logs the user in, obtains the authentication token, and passes it back down to the web application.
Obtain Boostlingo authentication token via API endpoint
POST https://app.boostlingo.com/api/web/account/signin
Request Model
{
"email": "<string>",
"password": "<string>"
}
Response Model
token
is what will be needed by the boostlingo sdk
{
"userAccountId": "<integer>",
"refreshToken": "<string>",
"role": "<string>",
"token": "<string>",
"companyAccountId": "<integer>"
}
Quickstart
This is a working example that will demonstrate how to Boostlingo calls.
Now let’s go to the Quickstart folder. Then run pod install --verbose
to download and build dependencies.
Update the placeholder of TOKEN with the token you got from the API.
BoostlingoSDK(authToken: "your token", region: "us", logger: BLPrintLogger())
Create instance of Boostlingo class and load dictionaries
We recommend you do this only once. The Boostlingo library will cache specific data and create instances of classes that do not need to be refreshed very frequently. The next step is typically to pull down the call dictionaries. Whether you expose these directly or are just mapping languages and service types with your internal types, loading these lists will almost definitely be required. In this example we populate a series of select dropdown inputs.
// For production builds use BLNullLogger() or your custom logger
boostlingo = BoostlingoSDK(authToken: self.tfToken.text ?? "", region: self.selectedRegion!, logger: BLPrintLogger())
boostlingo!.getCallDictionaries() { [weak self] (callDictionaries, error) in
guard let self else { return }
if error == nil {
self.languages = callDictionaries?.languages
self.serviceTypes = callDictionaries?.serviceTypes
self.genders = callDictionaries?.genders
self.selectedLanguageFrom = self.languages?.first(where: { item -> Bool in return item.id == 4 })?.id
self.selectedLanguageTo = self.languages?.first(where: { item -> Bool in return item.id == 1 })?.id
self.selectedServiceType = self.serviceTypes?.first(where: { item -> Bool in return item.id == 1 })?.id
self.selectedGender = self.genders?.first?.id
self.updateUI()
self.state = .authenticated
}
else {
self.state = .notAuthenticated
let message: String
switch error! {
case BLError.apiCall(_, let statusCode):
message = "\(error!.localizedDescription), statusCode: \(statusCode)"
break
default:
message = error!.localizedDescription
break
}
self.showErrorMessage(message)
}
}
Implement BLCallDelegate
// MARK: - BLCallDelegate
func callDidConnect(_ call: BLCall, participants: [BLParticipant]) {
DispatchQueue.main.async {
self.call = call as? BLVoiceCall
self.callId = self.call?.callId
self.delegate?.callId = self.callId
self.swMute.isOn = call.isMuted
self.state = .inprogress(interpreterName: self.call?.interlocutorInfo?.requiredName)
}
}
func callDidDisconnect(_ error: Error?) {
DispatchQueue.main.async {
self.call = nil
self.state = .nocall
let title = error != nil ? "Error" : "Info"
let message = error != nil ? "Call did disconnect with error: \(error!.localizedDescription)" : "Call did disconnect"
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { [weak self] (alert: UIAlertAction!) in
guard let self = self else {
return
}
self.navigationController?.popViewController(animated: true)
}))
self.present(alert, animated: true)
}
}
func callDidFailToConnect(_ error: Error?) {
DispatchQueue.main.async {
self.call = nil
self.state = .nocall
let title = error != nil ? "Error" : "Info"
let message = error != nil ? "Call did fail to connect with error: \(error!.localizedDescription)" : "Call did fail to connect"
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { [weak self] (alert: UIAlertAction!) in
guard let self = self else {
return
}
self.navigationController?.popViewController(animated: true)
}))
self.present(alert, animated: true)
}
}
func callParticipantConnected(_ participant: BLParticipant, call: BLCall) {
print("Participants: \(call.participants.count)")
for p in call.participants {
printParticipant(p)
}
}
func callParticipantUpdated(_ participant: BLParticipant, call: BLCall) {
print("Participants: \(call.participants.count)")
for p in call.participants {
printParticipant(p)
}
}
func callParticipantDisconnected(_ participant: BLParticipant, call: BLCall) {
print("Participants: \(call.participants.count)")
for p in call.participants {
printParticipant(p)
}
}
private func printParticipant(_ participant: BLParticipant) {
print("identity: \(participant.identity), isAudioEnabled: \(participant.isAudioEnabled), isVideoEnabled: \(participant.isVideoEnabled), muteActionIsEnabled: \(participant.muteActionIsEnabled), removeActionIsEnabled: \(participant.removeActionIsEnabled), requiredName: \(participant.requiredName), participantType: \(participant.participantType), rating: \(String(describing: participant.rating)), companyName: \(String(describing: participant.companyName)), state: \(participant.state)")
}
Retriving pre-call custom form
boostlingo.getPreCallCustomForm() { [weak self] customFormData, error in
guard let self else { return }
if let error {
self.showErrorMessage(error.localizedDescription)
return
}
// Show you custom form UI here using the customFormData
let fieldData = customFormData?.fields.map({ customField in
CustomFieldDto(
fieldId: customField.fieldId,
value: // user answer
)
}) ?? []
}
You should add answers collected from user to the fieldData
list which can be used later for creating a call request:
let callRequest = CallRequest(
languageFromId: self.selectedLanguageFrom!,
languageToId: self.selectedLanguageTo!,
serviceTypeId: self.selectedServiceType!,
genderId: self.selectedGender,
isVideo: true,
data: [
AdditionalField(
key: "CustomKey",
value: "CustomValue"
)
],
fieldData: fieldData
)
Custom form fields
public struct CheckBoxCustomField: CustomField, Hashable {
public let fieldId: Int64
public let fieldTypeId: Int
public let label: String
public let readonly: Bool
public let required: Bool
public var value: [Int64]?
public let options: [CustomFieldOption]
}
public struct EditTextCustomField: CustomField, Hashable {
public let fieldId: Int64
public let fieldTypeId: Int
public let label: String
public let readonly: Bool
public let required: Bool
public var value: String?
public let fieldType: FieldType
}
public struct ListMultipleCustomField: CustomField, Hashable {
public let fieldId: Int64
public let fieldTypeId: Int
public let label: String
public let readonly: Bool
public let required: Bool
public var value: [Int64]?
public let options: [CustomFieldOption]
}
public struct ListSingleCustomField: CustomField, Hashable {
public let fieldId: Int64
public let fieldTypeId: Int
public let label: String
public let readonly: Bool
public let required: Bool
public var value: Int64?
public let options: [CustomFieldOption]
}
public struct RadioButtonCustomField: CustomField, Hashable {
public let fieldId: Int64
public let fieldTypeId: Int
public let label: String
public let readonly: Bool
public let required: Bool
public var value: Int64?
public let options: [CustomFieldOption]
}
Placing a voice call
Before placing a call you will need to check record permission:
private func checkRecordPermission(completion: @escaping (_ permissionGranted: Bool) -> Void) {
let permissionStatus: AVAudioSession.RecordPermission = AVAudioSession.sharedInstance().recordPermission
switch permissionStatus {
case .granted:
// Record permission already granted.
completion(true)
break
case .denied:
// Record permission denied.
completion(false)
break
case .undetermined:
// Requesting record permission.
// Optional: pop up app dialog to let the users know if they want to request.
AVAudioSession.sharedInstance().requestRecordPermission({ (granted) in
completion(granted)
})
break
default:
completion(false)
break
}
}
boostlingo!.chatDelegate = self
boostlingo!.makeVoiceCall(callRequest: callRequest!, delegate: self) { [weak self] call, error in
guard let self else { return }
if let error {
self.state = .nocall
let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { [weak self] (alert: UIAlertAction!) in
guard let self = self else {
return
}
self.navigationController?.popViewController(animated: true)
}))
self.present(alert, animated: true)
return
}
self.call = call
self.state = .calling
}
Placing a video call
You don’t have to check the camera permission, the sdk will do it by itself. But you will need to provide TVIVideoView container for the remote and local video tracks.
vRemoteVideo.contentMode = .scaleAspectFit
vLocalVideo.contentMode = .scaleAspectFit
boostlingo!.chatDelegate = self
boostlingo!.makeVideoCall(callRequest: callRequest!, localVideoView: vLocalVideo, delegate: self) { [weak self] call, error in
guard let self else { return }
if let error {
self.state = .nocall
let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { [weak self] (alert: UIAlertAction!) in
guard let self = self else {
return
}
self.navigationController?.popViewController(animated: true)
}))
self.present(alert, animated: true)
return
}
self.call = call
self.state = .calling
}
// Adding a renderer for a participant
call.addRenderer(for: participant.identity, renderer: self.vRemoteVideo)
Using the chat functionality
Subscribe for the chat related callback using BLChatDelegate.
boostlingo!.chatDelegate = self
// MARK: - BLChatDelegate
func chatConnected() {
}
func chatDisconnected() {
}
func chatMessageRecieved(message: ChatMessage) {
DispatchQueue.main.async {
let alert = UIAlertController(title: "Chat Message Recieved", message: message.text, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(alert, animated: true)
}
}
Sending messages.
boostlingo!.sendChatMessage(text: "Test") { [weak self] message, error in
guard let self else { return }
if let error = error {
let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(alert, animated: true)
return
} else {
let alert = UIAlertController(title: "Success", message: "Message sent", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(alert, animated: true)
return
}
}
Geting profile image URL
Getting requestor profile image url.
boostlingo!.getProfile() { profile, error in
// Get the requestor profile URL
let url = profile?.imageInfo?.url(size: 64)
}
Getting a participant profile image url from BLCall.
// Get the interpreter profile image URL
let url = call?.participants.first?.imageInfo?.url(size: nil)
More Documentation
You can find more documentation and useful information below: