AzureCommunicationUI/sdk/AzureCommunicationUIChat/Sources/Service/Chat/ChatSDKWrapper.swift (300 lines of code) (raw):
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
import AzureCore
import AzureCommunicationChat
import Foundation
// swiftlint:disable type_body_length
class ChatSDKWrapper: NSObject, ChatSDKWrapperProtocol {
let chatEventsHandler: ChatSDKEventsHandling
private let logger: Logger
private let chatConfiguration: ChatConfiguration
private let threadId: String
private var chatClient: ChatClient?
private var chatThreadClient: ChatThreadClient?
private var pagedCollection: PagedCollection<ChatMessage>?
init(logger: Logger,
chatEventsHandler: ChatSDKEventsHandling,
chatConfiguration: ChatConfiguration,
chatThreadId: String) {
self.logger = logger
self.chatEventsHandler = chatEventsHandler
self.chatConfiguration = chatConfiguration
self.threadId = chatThreadId
super.init()
}
deinit {
logger.debug("ChatSDKWrapper deallocated")
}
func initializeChat() async throws {
do {
try createChatClient()
try createChatThreadClient()
// Make request to ChatSDK to verfy token
_ = try await retrieveChatThreadProperties().topic
try registerRealTimeNotifications()
} catch {
throw error
}
}
func getInitialMessages() async throws -> [ChatMessageInfoModel] {
do {
let listChatMessagesOptions = ListChatMessagesOptions(
maxPageSize: chatConfiguration.pageSize)
return try await withCheckedThrowingContinuation { continuation in
chatThreadClient?.listMessages(withOptions: listChatMessagesOptions) { result, _ in
switch result {
case .success(let messagesResult):
self.pagedCollection = messagesResult
let messages = self.pagedCollection?.pageItems?
.map({
$0.toChatMessageInfoModel(
localUserId: self.chatConfiguration.identifier.rawId)
})
continuation.resume(returning: messages?.reversed() ?? [])
case .failure(let error):
self.pagedCollection = nil
continuation.resume(throwing: error)
}
}
}
} catch {
logger.error("Retrieve Thread Topic failed: \(error)")
throw error
}
}
func retrieveChatThreadProperties() async throws -> ChatThreadInfoModel {
do {
return try await withCheckedThrowingContinuation { continuation in
chatThreadClient?.getProperties { result, _ in
switch result {
case .success(let threadProperties):
let topic = threadProperties.topic
let createdBy = threadProperties.createdBy.stringValue
self.logger.info("Retrieved thread topic: \(topic) and createdBy: \(createdBy)")
let chatThreadInfo = ChatThreadInfoModel(topic: topic,
receivedOn: Iso8601Date(),
createdBy: createdBy)
continuation.resume(returning: chatThreadInfo)
case .failure(let error):
self.logger.error("Retrieve Thread Properties failed: \(error.errorDescription ?? "")")
continuation.resume(throwing: error)
}
}
}
} catch {
logger.error("Retrieve Thread Properties failed: \(error)")
throw error
}
}
func getListOfParticipants() async throws -> [ParticipantInfoModel] {
do {
let participantsPageSize: Int32 = 200
let listParticipantsOptions = ListChatParticipantsOptions(maxPageSize: participantsPageSize)
let pagedCollectionResult = try await chatThreadClient?.listParticipants(
withOptions: listParticipantsOptions)
guard let pagedResult = pagedCollectionResult,
let items = pagedResult.items else {
return []
}
var allChatParticipants = items.map({
$0.toParticipantInfoModel(self.chatConfiguration.identifier.rawId)
})
while !pagedResult.isExhausted {
let nextPage = try await pagedResult.nextPage()
let pageParticipants = nextPage.map {
$0.toParticipantInfoModel(self.chatConfiguration.identifier.rawId)
}
allChatParticipants.append(contentsOf: pageParticipants)
}
return allChatParticipants
} catch {
logger.error("Get List of Participants failed: \(error)")
throw error
}
}
func getPreviousMessages() async throws -> [ChatMessageInfoModel] {
do {
guard let messagePagedCollection = self.pagedCollection else {
return try await self.getInitialMessages()
}
return try await withCheckedThrowingContinuation { continuation in
if messagePagedCollection.isExhausted {
continuation.resume(returning: [])
} else {
messagePagedCollection.nextPage { result in
switch result {
case .success(let messagesResult):
let previousMessages = messagesResult.map({
$0.toChatMessageInfoModel(
localUserId: self.chatConfiguration.identifier.rawId)
})
continuation.resume(returning: previousMessages)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
} catch {
logger.error("Retrieve previous messages failed: \(error)")
throw error
}
}
func sendMessage(content: String, senderDisplayName: String) async throws -> String {
do {
let messageRequest = SendChatMessageRequest(
content: content,
senderDisplayName: senderDisplayName
)
return try await withCheckedThrowingContinuation { continuation in
chatThreadClient?.send(message: messageRequest) { result, _ in
switch result {
case let .success(result):
continuation.resume(returning: result.id)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
} catch {
logger.error("Retrieve Thread Topic failed: \(error)")
throw error
}
}
func editMessage(messageId: String, content: String) async throws {
do {
let messageRequest = UpdateChatMessageRequest(
content: content
)
return try await withCheckedThrowingContinuation { continuation in
chatThreadClient?.update(message: messageId, parameters: messageRequest) { result, _ in
switch result {
case .success:
continuation.resume()
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
} catch {
logger.error("Edit Message failed: \(error)")
throw error
}
}
func deleteMessage(messageId: String) async throws {
do {
return try await withCheckedThrowingContinuation { continuation in
chatThreadClient?.delete(message: messageId) { result, _ in
switch result {
case .success:
continuation.resume()
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
} catch {
logger.error("Delete Message failed: \(error)")
throw error
}
}
func sendReadReceipt(messageId: String) async throws {
do {
return try await withCheckedThrowingContinuation { continuation in
chatThreadClient?.sendReadReceipt(
forMessage: messageId,
withOptions: SendChatReadReceiptOptions()) { result, error in
switch result {
case .success:
continuation.resume(returning: Void())
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
} catch {
logger.error("Failed to send read receipt: \(error)")
throw error
}
}
func sendTypingIndicator() async throws {
do {
return try await withCheckedThrowingContinuation { continuation in
self.chatThreadClient?.sendTypingNotification(from: self.chatConfiguration.displayName) { result, _ in
switch result {
case .success:
continuation.resume(returning: Void())
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
} catch {
self.logger.error("Send Typing Indicator failed: \(error)")
throw error
}
}
private func createChatClient() throws {
do {
logger.info("Creating Chat Client...")
let appId = self.chatConfiguration.diagnosticConfig.tags
.joined(separator: "/")
let telemetryOptions = TelemetryOptions(applicationId: appId)
let clientOptions = AzureCommunicationChatClientOptions(telemetryOptions: telemetryOptions)
self.chatClient = try ChatClient(
endpoint: self.chatConfiguration.endpoint,
credential: self.chatConfiguration.credential,
withOptions: clientOptions)
} catch {
logger.error("Create Chat Client failed: \(error)")
throw error
}
}
private func createChatThreadClient() throws {
do {
logger.info("Creating Chat Thread Client...")
self.chatThreadClient = try chatClient?.createClient(
forThread: self.threadId)
} catch {
logger.error("Create Chat Thread Client failed: \(error)")
throw error
}
}
private func registerRealTimeNotifications() throws {
self.chatClient?.startRealTimeNotifications { [self] (result: Result<Void, AzureError>) in
switch result {
case .success:
logger.info("Real-time notifications started.")
self.registerEvents()
case .failure(let error):
logger.error("Failed to start real-time notifications. \(error)")
}
}
}
func unregisterRealTimeNotifications() async throws {
guard let client = self.chatClient else {
return
}
do {
return try await withCheckedThrowingContinuation { continuation in
client.stopRealTimeNotifications()
continuation.resume(returning: Void())
}
} catch {
self.logger.error("Stop real time notification failed: \(error)")
throw error
}
}
private func registerEvents() {
guard let client = self.chatClient else {
return
}
client.register(event: .realTimeNotificationConnected, handler: chatEventsHandler.handle)
client.register(event: .realTimeNotificationDisconnected, handler: chatEventsHandler.handle)
client.register(event: .chatMessageReceived, handler: chatEventsHandler.handle)
client.register(event: .chatMessageEdited, handler: chatEventsHandler.handle)
client.register(event: .chatMessageDeleted, handler: chatEventsHandler.handle)
client.register(event: .typingIndicatorReceived, handler: chatEventsHandler.handle)
client.register(event: .readReceiptReceived, handler: chatEventsHandler.handle)
client.register(event: .chatThreadDeleted, handler: chatEventsHandler.handle)
client.register(event: .chatThreadPropertiesUpdated, handler: chatEventsHandler.handle)
client.register(event: .participantsAdded, handler: chatEventsHandler.handle)
client.register(event: .participantsRemoved, handler: chatEventsHandler.handle)
}
}
// swiftlint:enable type_body_length