Sendbird Desk SDK for iOS
目录
简介
Sendbird Desk 通过实时应用内支持,使客户参与度更强。Desk SDK 允许您轻松初始化、配置并将客户支持相关功能构建到您的 iOS 应用程序中。
如何工作
Sendbird Desk 是 Sendbird Chat 平台的插件,用于管理工单,所有 Desk 事件都通过 聊天 SDK 处理。
每个工单都会分配适当的客服代表,并将被定向到聊天组的频道,该频道通过 Sendbird Chat SDK 实现实时消息。
概念
以下是 Desk SDK 的一些主要组件。
- 频道:支持请求的多种方式,例如来自不同操作系统平台的内联聊天或社交媒体(如 Facebook 和 Instagram)。
- 工单:当客服代表和客户开始对话时创建工单,它是客户咨询的单元。工单有五种类型。
- 客服代表:客服代表接收请求并在 Sendbird 控制台 中处理对话。
- 管理员:管理员是具有管理整体控制台设置和工单额外权限的客服代表。
- 消息:Desk 有两种主要消息类型,并进一步分为子类型。以下表格显示了消息的层次结构。
发送者 | 子类型 | |
---|---|---|
用户消息 | 客服或客户 | 富文本消息 |
管理员消息 | 由 Desk 服务器发送,没有特定发送者 | 通知消息和系统消息 |
关于Sendbird Desk SDK for iOS的更多信息
了解更多关于Sendbird Desk SDK for iOS的信息,请访问Desk SDK for iOS 文档。如果您对错误和功能请求有任何评论或问题,请访问Sendbird 社区。
开始前的准备
本节将展示使用Sendbird Desk SDK for iOS所需检查的前提条件。
要求
Sendbird Desk SDK for iOS的最低要求如下
- iOS 11+
- Sendbird Chat SDK for iOS 4.6.7+
开始使用
本节将为您提供开始使用Sendbird Desk SDK for iOS所需的信息。
尝试示例应用
我们的示例应用展示了Sendbird Desk SDK的核心功能。从我们的GitHub存储库下载应用程序,了解您可以使用实际SDK做什么,并开始构建您自己的项目。
步骤1:从仪表板创建Sendbird应用程序
一个Sendbird应用程序包含了聊天服务所需的一切,包括用户、消息和频道。要创建应用程序
- 访问Sendbird仪表板,输入您的电子邮件和密码,创建新账户。您也可以使用Google账户进行注册。
- 当设置向导提示时,输入您的组织信息以管理Sendbird应用程序。
- 最后,完成设置后在仪表板主页面出现时,点击右上角的创建+。
无论平台如何,每个应用程序只能集成一个Sendbird应用程序;然而,应用程序支持Sendbird提供的所有平台之间的通信,无需任何额外设置。
注意:所有数据都限于单个应用程序的范围,因此不同Sendbird应用程序中的用户无法互相聊天。
步骤2:下载和安装Desk SDK
如果您熟悉在项目中使用外部库或SDK,安装Desk SDK很简单。您可以使用以下两种方法之一安装Desk SDK
- 创建或编辑您的
Podfile
文件。
# Uncomment the next line to define a global platform for your project
platform :ios, '11.0'
target 'YourTarget' do
# Uncomment the next line if you're using Swift or would like to use dynamic frameworks
use_frameworks!
# Pods for YourTarget
pod 'SendBirdDesk'
end
- 运行
pod install
。 - 打开
YOUR_PROJECT.xcworkspace
。
或者,您可以下载最新的 Desk SDK for iOS。将可在 Github 仓库中找到的 Desk SDK 复制到您的项目目录中,并确保在您的工文件中也包含了该库。
创建您的第一个工单
安装完成后,可以为代理商和客户之间的通讯创建一个工单。按照以下分步说明来创建您的第一个工单。
步骤 1:初始化 Desk SDK
启动客户端应用程序时必须初始化“SBDSKMain”实例。在应用完成启动的“applicationDidFinishLaunching”方法中调用“SendbirdChat.initialize(params:)”和“SBDSKMain.initializeDesk()”方法。首先由仪表板中的“APP_ID”调用“initialize(params:)”,该“APP_ID”属于您的 Sendbird 应用程序。
class AppDelegate: UIApplicationDelegate {
func applicationDidFinishLaunching(_ application: UIApplication) {
let initParams = InitParams(applicationId: APP_ID)
SendbirdChat.initialize(params: initParams)
SBDSKMain.initializeDesk()
}
}
注意:使用 Sendbird Chat SDK 构建,Sendbird Desk SDK 是一个插件,用于通过工单处理客户询问。工单中的消息由 Sendbird Chat 平台处理,每个工单都与 Sendbird Chat 中的组频道对应。由于这种交互,Desk 和 Chat SDK 都应使用相同的
APP_ID
。
步骤 2:验证客户身份
客户可以通过多种类型的渠道请求支持:应用内聊天或社交媒体,如 Facebook、Instagram 和 Twitter。要使用 Desk SDK 的这些支持功能, SendbirdDesk 实例应根据请求的渠道与 Sendbird 服务器连接。
- Sendbird Chat 平台:使用
SendbirdChat.connect()
和SBDSKMain.authenticate()
方法以及他们的用户标识进行认证。 - 社交媒体平台:无需认证,因为顾客会自动使用社交媒体账户在 仪表板 登记。
认证后,顾客可以根据 Sendbird Chat 平台与客服进行实时聊天。
SendbirdChat.connect(userId: USER_ID, authToken: ACCESS_TOKEN) { (user, error) in
guard error == nil else {
// Handle error
return
}
// Use the same user ID and access token(`authToken`) used in the SendbirdChat.connect()
SBDSKMain.authenticate(withUserId: USER_ID, accessToken: ACCESS_TOKEN) { (error) in
guard error == nil else {
// Handle error
return
}
// SendBirdDesk is now initialized, and the customer is authenticated.
}
}
备注:来自 Sendbird Chat 平台的客户 表示已通过 Chat SDK 认证的用戶。如果您同时实现 Chat SDK 和 Desk SDK,首先使用用户标识和访问令牌 将用户连接到 Sendbird 服务器。
第3步:创建工单
使用 SBDSKTicket.createTicket()
方法在客户初始消息之前或之后创建新的工单。返回的工单将包含可以通过 ticket.channel
访问的会话实例。因此,您可以使用 SendbirdChat
SDK 向该会话发送消息。
SBDSKTicket.createTicket(with: TITLE, userName: USER_NAME) { (ticket, error) in
guard error == nil else {
// Handle error.
return
}
// The ticket is created. Agents and customers can chat with each other by sending a message through the ticket.channel.sendUserMessage() or sendFileMessage().
}
在 Sendbird 服务器上成功创建工单后,您可以通过服务器的回调访问该工单及其会话。
在客户发送第一条消息之前,客服无法在仪表板上看到工单,且不会发生工单分配。一旦开始对话,Desk Dashboard 会在发送和接收消息的同时将工单分配给可用的客服。
在创建工单时,您可以使用以下参数传递到 SBDSKTicket.createTicket()
的参数。
参数 | 类型 | 描述 |
---|---|---|
TICKET_TITLE | 字符串 |
指定工单的标题。 |
USER_NAME | 字符串 |
指定提交或接收工单的用户姓名。 |
GROUP_KEY | 字符串 |
指定特定团队的标识符。 |
CUSTOM_FIELDS | [String: String] |
指定由 键值 形式的自定义项目组成的工单附加信息。只有仪表板中按 设置 > 工单 字段注册的自定义字段可以作为键使用。 |
PRIORITY | SBDSKTicketPriority |
指定工单的优先级值。数值越大,优先级越高。有效值为 .low、.medium、.high 和 .urgent。 |
RELATED_CHANNEL_URLS | [String] |
指定与该工单相关的 Sendbird Chat 平台的群组会话,由会话 URL 和会话名称组成。最多可以添加3个相关会话。 |
var customFields = [String: String]()
customFields["product"] = "desk"
customFields["line"] = "14"
customFields["select"] = "option2"
SBDSKTicket.createTicket(with: TICKET_TITLE, userName: USER_NAME, groupKey: GROUP_KEY customFields: customFields, priority: PRIORITY, relatedChannels: RELATED_CHANNEL_URLS) { (ticket, error) in
guard error == nil else {
// Handle error.
return
}
// The ticket is created with parameters.
}
实施指南
本部分详细说明了如何从您的客户端应用程序处理和关闭工单。
为工单添加自定义信息
使用 ticket.setCustomFields()
方法为工单添加附加信息。
var customFields = [String: String]()
customFields["product"] = "Desk"
customFields["line"] = "\(30)"
ticket.setCustomFields(customFields) { (error) in
guard error == nil else {
// Handle error.
return
}
// Custom fields for the ticket are set.
// Some fields can be ignored if their keys aren't registered in the dashboard.
}
注意:只能使用在您的仪表板 Desk > 设置 > 票务字段 中注册的自定义字段作为键。
为客户添加自定义信息
使用 SBDSKMain
的 setCustomerCustomFields()
方法让您的客户添加关于他们的附加信息。
var customFields = [String: String]()
customFields["gender"] = "Female"
customFields["age"] = "\(30)"
SBDSKMain.setCustomerCustomFields(customFields) { (error) in
guard error == nil else {
// Handle error.
return
}
}
// Custom fields for the customer are set.
// Some fields can be ignored if their keys aren't registered in the dashboard.
注意:只能使用在您的仪表板 Desk > 设置 > 客户字段 中注册的自定义字段作为键。
添加相关频道
相关频道表示Sendbird Chat平台上与工单相关的组频道。在创建工单时,将相关组频道的channel_url
作为参数传递给SBDSKTicket.createTicket()
方法中的relatedChannels
。要更新相关频道,请使用SBDSKTicket.setRelatedChannels()
。回调中的SBDSKTicket.relatedChannels
属性指示相关频道的组频道对象,并包含频道名称和它们的URL。
SBDSKTicket.createTicket(withTitle: TITLE, userName: USER_NAME) { (ticket, error) in
guard error == nil else {
// Handle error.
return
}
// The ticket is created. Agents and customers can chat with each other by sending a message through the ticket.channel.sendUserMessage() or sendFileMessage().
...
ticket.setRelatedChannels(RELATED_CHANNEL_URLS) { (error) in
guard error == nil else {
// Handle error.
return
}
// The ticket.relatedChannels property has been updated.
}
}
注意:每个工单最多可以添加3个相关频道。
添加URL预览
通过URL预览,您的应用程序用户在打开链接之前可以了解他们将会得到什么。
要使用URL预览,每个文本消息都应该检查是否包含任何URL。其次,分析URL的HTML源代码以查找OpenGraph元数据。然后,将提取的OG元数据设置为JSON对象,并将其字符串化以作为参数传递给UserMessageUpdateParams
类的data属性,该类用于GroupChannel updateUserMessage(messageId:params:data:completionHandler:
)方法的参数。带有URL预览的更新消息将通过BaseChannelDelegate
的channel(_:didUpdate:)
代理方法发送到客户端应用。
ticket.channel?.sendUserMessage(TEXT, completionHandler: { userMessage, error in
guard let userMessage = userMessage, error == nil else {
// Handle error.
return
}
let detector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let match = detector?.firstMatch(in: userMessage.message, options: [], range: NSMakeRange(0, userMessage.message.count))
guard let url = match?.url else { // No matching URL
return
}
URLSession().dataTask(with: url) { (data, response, error) in
guard error == nil, let data = data else {
// Handle error.
return
}
guard let httpResponse = response as? HTTPURLResponse,
let contentType = httpResponse.allHeaderFields["Content-Type"] as? String,
contentType.contains("text/html") else { return }
let htmlBody = String(data: data, encoding: .utf8)
// Extract ogTitle, ogImage, ogUrl, ogSiteName, ogDescription from htmlBody.
// Refer to Open Graph Protocol(https://ogp.me/), and other open source implementations of OG tag parsing for further details.
let urlPreview = ["type": "SENDBIRD_DESK_URL_PREVIEW",
"body": [
"title": ogTitle,
"image": ogImage,
"url": ogUrl,
"site_name": ogSiteName,
"description": ogDescription
]
] as [String: Any]
// Stringified JSON object let jsonData = try? JSONSerialization.data(withJSONObject: urlPreview, options: [])
let params = UserMessageUpdateParams()
params.message = userMessage.message
params.data = jsonData?.base64EncodedString()
params.customType = "SENDBIRD_DESK_RICH_MESSAGE"
ticket.channel?.update(messageId: userMessage.messageId, params: params, completionHandler: { (userMessage, error) in
guard error == nil else {
// Handle error.
return
}
...
// Pass data to update(messageId:params:completionHandler:) method in the GroupChannel.
})
}
})
有许多从HTML主体提取OG元数据的方法。您还可以参考我们的GitHub存储库以了解我们使用的方法。
在BaseChannelDelegate
的channel(_:didUpdate:)
代理方法中,您可以在下面的UserMessage data
属性中找到URL预览的数据。
{
"type": "SENDBIRD_DESK_URL_PREVIEW",
"body": {
"url": "https://sendbird.com/",
"site_name": "Sendbird",
"title": "A Complete Chat Platform, Messaging and Chat SDK and API",
"description": "Sendbird's chat, voice and video APIs and SDKs connect users through immersive, modern communication solutions...",
"image": "https://6cro14eml0v2yuvyx3v5j11j-wpengine.netdna-ssl.com/wp-content/uploads/sendbird_thumbnail.png"
}
}
接收系统消息
管理员消息是系统发送的可定制消息,分为两种类型。.通知是发送并显示给客户和坐席的消息,例如欢迎消息或延迟消息。.系统消息是在票据详细信息视图中,当票据状态或指派人有变化时发送并显示给坐席的消息。
注意:您可以在 Desk > 设置 > 触发器 中自定义通知,并在仪表板中在 Desk > 设置 > 系统消息 中自定义系统消息。
当客户端应用程序通过 BaseChannelDelegate
的 channel(_:didReceive:)
方法收到消息时,通过 custom_type
的值区分系统消息和通知消息,其子类型在 data
中如下指定。
{
"message_id": 40620745,
"type": "ADMM",
"custom_type": "SENDBIRD_DESK_ADMIN_MESSAGE_CUSTOM_TYPE",
"data": "{\"type\": \"SYSTEM_MESSAGE_TICKET_ASSIGNED_BY_SYSTEM\", \"ticket\": <Ticket Object>}",
"message": "The ticket is automatically assigned to Cindy."
}
注意:当
data
中有SYSTEM_MESSAGE_TICKET_TRANSFERRED_BY_AGENT
时,transfer
才出现。
系统消息仅供代理显示。请参阅以下示例代码,以避免向客户显示系统消息。
public static func isVisible(message: BaseMessage) -> Bool {
if let message = message as? AdminMessage, let data = Data(base64Encoded: message.data), data.isEmpty == false {
let isSystemMessage = (message.customType == "ADMIN_MESSAGE_CUSTOM_TYPE")
let dataObject = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
let type = dataObject?["type"] as? String
return !isSystemMessage &&
type != EVENT_TYPE_ASSIGN &&
type != EVENT_TYPE_TRANSFER &&
type != EVENT_TYPE_CLOSE
}
return true
}
请求确认关闭票据
虽然管理员有权限直接关闭票据,但根据代理权限设置,代理人可以像管理员一样关闭票据或请求客户是否关闭票据。确认请求消息可以有3种状态如下。
状态 | 描述 |
---|---|
WAITING | 当代理发送确认请求消息时设置。 |
CONFIRMED | 当客户同意关闭票据时设置。(默认:true) |
DECLINED | 当客户拒绝关闭工单时设置该标志。(默认: 未选中) |
当客户回复消息时,将通过调用 SBDSKTicket.confirmEndOfChat()
方法,将响应 是(同意)或 否(拒绝)发送到 Desk 服务器,分别以 CONFIRMED
或 DECLINED
形式。
SBDSKTicket.confirmEndOfChat(with: USER_MESSAGE, confirm: true|false) { (ticket, error) in
guard error == nil else {
// Handle error.
return
}
// You can update the UI of the message. For example, you can hide YES and NO buttons.
}
Sendbird 服务器通过 BaseChannelDelegate
的 channel(_:didUpdate:)
方法通知客户客户端应用程序更新。
func channel(_ channel: BaseChannel, didUpdate message: BaseMessage) {
SBDSKTicket.getByChannelUrl(channel.channelURL) { (ticket, error) in
guard error == nil else {
// Handle error
return
}
if let data = Data(base64Encoded: message.data), data.isEmpty == false {
let dataObject = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
let type = dataObject?["type"] as? String
let isClosureInquired = (type == "SENDBIRD_DESK_INQUIRE_TICKET_CLOSURE")
if isClosureInquired {
let closureInquiry = dataObject?["body"] as? [String: Any]
let state = closureInquiry?["state"] as? String
switch state {
case "CONFIRMED":
// TODO: Implement your code for the UI when the customer confirms to close the ticket.
case "DECLINED":
// TODO: Implement your code for the UI when the customer declines to close the ticket.
case "WAITING":
// TODO: Implement your code for the UI when there is no response from the customer.
default: break
}
}
}
}
}
注意:您可以在
BaseChannelDelegate
的channel(_:didUpdate:)
代理方法中的message.data
属性中找到以下字符串化的JSON
对象。
{
"type": "SENDBIRD_DESK_INQUIRE_TICKET_CLOSURE",
"body": {
"state": "CONFIRMED"
}
}
请求客户反馈
您可以在关闭工单后立即向客户发送消息,询问他们对通过工单提供的支持是否满意。当在仪表板中开启 客户满意度评分 功能后,客户将收到一条请求评分并留下评论作为反馈的消息。该消息具有以下两种状态。
状态 | 描述 |
---|---|
WAITING | 当代理发送客户反馈请求消息时设置。 |
CONFIRMED | 当客户发送响应时设置。 |
当客户回复消息时,通过调用 ticket.submitFeedback()
方法将评分和评论发送到 Desk 服务器。然后,确认请求消息的状态将更改为 CONFIRMED
。
ticket.submitFeedback(with: USER_MESSAGE, score: SCORE, comment: COMMENT) { (ticket, error) in
guard error == nil else {
// Handle error.
return
}
...
}
Sendbird Desk 服务器通过 BaseChannelDelegate
的 channel(_:didUpdate:)
代理方法通知客户的客户端应用程序更新。
func channel(_ channel: BaseChannel, didUpdate message: BaseMessage) {
SBDSKTicket.getByChannelUrl(channel.channelURL) { (ticket, error) in
guard error == nil else {
// Handle error.
return
}
if let data = Data(base64Encoded: message.data), data.isEmpty == false {
let dataObject = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
let type = dataObject?["type"] as? String
let isFeedbackMessage = (type == "SENDBIRD_DESK_CUSTOMER_SATISFACTION")
if isFeedbackMessage {
let closureInquiry = dataObject?["body"] as? [String: Any]
let state = closureInquiry?["state"] as? String
switch state {
case "CONFIRMED":
// TODO: Implement your code for the UI when there is a response from the customer.
case "WAITING":
// TODO: Implement your code for the UI when there is no response from the customer.
default: break
}
}
}
}
}
注意:您可以在
BaseChannelDelegate
的channel(_:didUpdate:)
代理方法中的message.data
属性中找到以下字符串化的JSON
对象。
{
"type": "SENDBIRD_DESK_CUSTOMER_SATISFACTION",
"body": {
"state": "CONFIRMED",
"customerSatisfactionScore": 3,
"customerSatisfactionComment": "It was really helpful :)"
}
}
重新打开已关闭的工单
已关闭的工单可以通过使用 ticket.reopen()
方法重新打开。
ticket.reopen { (ticket, error) in
guard error == nil else {
// Handle error.
return
}
...
}
检索工单列表
您可以使用 SBDSKTicket.getOpenedList()
和 SBDSKTicket.getClosedList()
方法检索当前客户的所有开放和关闭工单。
注意:每次请求最多只能检索10个工单,按创建时间降序排列。
// getOpenedList()
SBDSKTicket.getOpenedList(withOffset: OFFSET) { (tickets, hasNext, error) in
guard error == nil else {
// Handle error.
return
}
// offset += tickets.size(); for the next tickets.
// TODO: Implement your code to display the ticket list.
}
// getClosedList()
SBDSKTicket.getClosedList(withOffset: OFFSET) { (tickets, hasNext, error) in
guard error == nil else {
// Handle error.
return
}
// offset += tickets.size(); for the next tickets.
// TODO: Implement your code to display the ticket list.
}
对于设置了自定义字段的工单,您可以在 getOpenList()
和 getClosedList()
中添加过滤器,按自定义字段的键和值对工单进行排序。
let customFieldFilter = ["subject": "doggy_doggy"]
SBDSKTicket.getOpenedList(withOffset: OFFSET, customFieldFilter: CUSTOM_FIELD_FILTER) { (tickets, hasNext, error) in
guard error == nil else {
// Handle error.
return
}
// offset += tickets.length; for the next tickets.
// TODO: Implement your code to display the ticket list.
}
检索特定工单
您可以通过工单的渠道URL检索特定的工单。
SBDSKTicket.getByChannelUrl(channel.channelURL) { (ticket, error) in
guard error == nil else {
// Handle error.
return
}
...
}
显示打开工单数量
您可以使用 SBDSKTicket.getOpenCount()
方法在您的客户端应用程序中显示打开工单的数量。
SBDSKTicket.getOpenCount { (count, error) in
guard error == nil else {
// Handle rror.
return
}
// TODO: Implement your code with the result vale.
}