sdk/communication/AzureCommunicationChat/Source/ChatThreadClient.swift (361 lines of code) (raw):
// --------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the ""Software""), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
// --------------------------------------------------------------------------
import AzureCommunicationCommon
import AzureCore
import Foundation
/// ChatThreadClient class for operations within a ChatThread.
// swiftlint:disable:next type_body_length
public class ChatThreadClient {
// MARK: Properties
public let threadId: String
private let endpoint: String
private let credential: CommunicationTokenCredential
private let options: AzureCommunicationChatClientOptions
private let service: ChatThread
// MARK: Initializers
/// Create a ChatThreadClient.
/// - Parameters:
/// - endpoint: The Communication Services endpoint.
/// - credential: The user credential.
/// - threadId: The chat thread id.
/// - options: Options used to configure the client.
public init(
endpoint: String,
credential: CommunicationTokenCredential,
threadId: String,
withOptions options: AzureCommunicationChatClientOptions
) throws {
self.threadId = threadId
self.endpoint = endpoint
self.credential = credential
self.options = options
guard let endpointUrl = URL(string: endpoint) else {
throw AzureError.client("Unable to form base URL")
}
// Internal options do not use the CommunicationSignalingErrorHandler
let internalOptions = AzureCommunicationChatClientOptionsInternal(
apiVersion: AzureCommunicationChatClientOptionsInternal.ApiVersion(options.apiVersion),
logger: options.logger,
telemetryOptions: options.telemetryOptions,
transportOptions: options.transportOptions,
dispatchQueue: options.dispatchQueue
)
let communicationCredential = TokenCredentialAdapter(credential)
let authPolicy = BearerTokenCredentialPolicy(credential: communicationCredential, scopes: [])
let client = try ChatClientInternal(
endpoint: endpointUrl,
authPolicy: authPolicy,
withOptions: internalOptions
)
self.service = client.chatThread
}
// MARK: Private Methods
/// Creates a PagedCollection from the given data and request.
/// - Parameters:
/// - data: The data to initialize the PagedCollection with.
/// - request: The HTTPRequest used to make the call.
/// - type: The type of the elements in the PagedCollection.
private func createPagedCollection<T: Codable>(
from data: Data?,
withRequest request: HTTPRequest?,
of _: T.Type
) throws -> PagedCollection<T> {
guard let request = request else {
throw AzureError.client("HTTPResponse does not contain httpRequest.")
}
guard let data = data else {
throw AzureError.client("HTTPResponse does not contain data.")
}
let decoder = JSONDecoder()
let codingKeys = PagedCodingKeys(
items: "value",
continuationToken: "nextLink"
)
let context = PipelineContext.of(keyValues: [
ContextKey.allowedStatusCodes.rawValue: [200, 401, 403, 429, 503] as AnyObject
])
return try PagedCollection<T>(
client: service.client,
request: request,
context: context,
data: data,
codingKeys: codingKeys,
decoder: decoder
)
}
/// Converts [ChatParticipant] to [ChatParticipantInternal] for internal use.
/// - Parameter participants: The array of ChatParticipants.
/// - Returns: An array of ChatParticipantInternal.
private func convert(participants: [ChatParticipant]) throws -> [ChatParticipantInternal] {
return try participants.map { participant -> ChatParticipantInternal in
let identifierModel = try IdentifierSerializer.serialize(identifier: participant.id)
return ChatParticipantInternal(
communicationIdentifier: identifierModel,
displayName: participant.displayName,
shareHistoryTime: participant.shareHistoryTime
)
}
}
// MARK: Public Methods
/// Get the ChatThreadProperties for the chat thread.
/// - Parameters:
/// - options: Get chat thread options.
/// - completionHandler: A completion handler that receives the chat thread properties on success.
public func getProperties(
withOptions options: GetChatThreadPropertiesOptions? = nil,
completionHandler: @escaping HTTPResultHandler<ChatThreadProperties>
) {
service.getChatThreadProperties(chatThreadId: threadId, withOptions: options) { result, httpResponse in
switch result {
case let .success(chatThreadProperties):
do {
let thread = try ChatThreadProperties(from: chatThreadProperties)
completionHandler(.success(thread), httpResponse)
} catch {
let azureError = AzureError.client(error.localizedDescription, error)
completionHandler(.failure(azureError), httpResponse)
}
case let .failure(error):
completionHandler(.failure(error), httpResponse)
}
}
}
/// Updates the ChatThread's topic.
/// - Parameters:
/// - topic: The topic.
/// - options: Update chat thread options.
/// - completionHandler: A completion handler that receives a status code on success.
public func update(
topic: String,
withOptions options: UpdateChatThreadPropertiesOptions? = nil,
completionHandler: @escaping HTTPResultHandler<Void>
) {
let updateChatThreadRequest = UpdateChatThreadRequestInternal(topic: topic)
service
.update(
chatThreadProperties: updateChatThreadRequest,
chatThreadId: threadId,
withOptions: options
) { result, httpResponse in
switch result {
case .success:
completionHandler(.success(()), httpResponse)
case let .failure(error):
completionHandler(.failure(error), httpResponse)
}
}
}
/// Sends a read receipt.
/// - Parameters:
/// - messageId: The id of the message to send a read receipt for.
/// - options: Send read receipt options.
/// - completionHandler: A completion handler that receives a status code on success.
public func sendReadReceipt(
forMessage messageId: String,
withOptions options: SendChatReadReceiptOptions? = nil,
completionHandler: @escaping HTTPResultHandler<Void>
) {
let sendReadReceiptRequest = SendReadReceiptRequest(chatMessageId: messageId)
service
.send(
chatReadReceipt: sendReadReceiptRequest,
chatThreadId: threadId,
withOptions: options
) { result, httpResponse in
switch result {
case .success:
completionHandler(.success(()), httpResponse)
case let .failure(error):
completionHandler(.failure(error), httpResponse)
}
}
}
/// Lists read receipts for the ChatThread.
/// - Parameters:
/// - options: List chat read receipts options.
/// - completionHandler: A completion handler that receives the list of read receipts on success.
public func listReadReceipts(
withOptions options: ListChatReadReceiptsOptions? = nil,
completionHandler: @escaping HTTPResultHandler<PagedCollection<ChatMessageReadReceipt>>
) {
service.listChatReadReceipts(chatThreadId: threadId, withOptions: options) { result, httpResponse in
switch result {
case .success:
// TODO: https://github.com/Azure/azure-sdk-for-ios/issues/644
// Construct a new PagedCollection of type ChatMessageReadReceipt
do {
let readReceipts = try self.createPagedCollection(
from: httpResponse?.data,
withRequest: httpResponse?.httpRequest,
of: ChatMessageReadReceipt.self
)
completionHandler(.success(readReceipts), httpResponse)
} catch {
let azureError = AzureError.client(error.localizedDescription, error)
completionHandler(.failure(azureError), httpResponse)
}
case let .failure(error):
completionHandler(.failure(error), httpResponse)
}
}
}
/// Sends a typing notification.
/// - Parameters:
/// - senderDisplayName: Display name for the typing notification.
/// - options: Send typing notification options
/// - completionHandler: A completion handler that receives a status code on success.
public func sendTypingNotification(
from senderDisplayName: String? = nil,
withOptions options: SendTypingNotificationOptions? = nil,
completionHandler: @escaping HTTPResultHandler<Void>
) {
// Send the displayName if provided
var request: SendTypingNotificationRequest?
if let displayName = senderDisplayName {
request = SendTypingNotificationRequest(senderDisplayName: displayName)
}
service
.send(typingNotification: request, chatThreadId: threadId, withOptions: options) { result, httpResponse in
switch result {
case .success:
completionHandler(.success(()), httpResponse)
case let .failure(error):
completionHandler(.failure(error), httpResponse)
}
}
}
/// Sends a message to a ChatThread.
/// - Parameters:
/// - message : Request that contains the message properties.
/// - options: A list of options for the operation.
/// - completionHandler: A completion handler that receives a status code on success.
public func send(
message: SendChatMessageRequest,
withOptions options: SendChatMessageOptions? = nil,
completionHandler: @escaping HTTPResultHandler<SendChatMessageResult>
) {
service
.send(chatMessage: message, chatThreadId: threadId, withOptions: options) { result, httpResponse in
switch result {
case let .success(sendMessageResult):
completionHandler(.success(sendMessageResult), httpResponse)
case let .failure(error):
completionHandler(.failure(error), httpResponse)
}
}
}
/// Gets a message by id.
/// - Parameters:
/// - messageId : The id of the message to get.
/// - options: Get chat message options
/// - completionHandler: A completion handler that receives the chat message on success.
public func get(
message messageId: String,
withOptions options: GetChatMessageOptions? = nil,
completionHandler: @escaping HTTPResultHandler<ChatMessage>
) {
service
.getChatMessage(
chatThreadId: threadId,
chatMessageId: messageId,
withOptions: options
) { result, httpResponse in
switch result {
case let .success(chatMessage):
do {
let message = try ChatMessage(from: chatMessage)
completionHandler(.success(message), httpResponse)
} catch {
let azureError = AzureError.client(error.localizedDescription, error)
completionHandler(.failure(azureError), httpResponse)
}
case let .failure(error):
completionHandler(.failure(error), httpResponse)
}
}
}
/// Updates a message.
/// - Parameters:
/// - message: The message id.
/// - parameters: The UpdateChatMessageRequest.
/// - options: Update chat message options
/// - completionHandler: A completion handler that receives a status code on success.
public func update(
message messageId: String,
parameters: UpdateChatMessageRequest,
withOptions options: UpdateChatMessageOptions? = nil,
completionHandler: @escaping HTTPResultHandler<Void>
) {
service
.update(
chatMessage: parameters,
chatThreadId: threadId,
chatMessageId: messageId,
withOptions: options
) { result, httpResponse in
switch result {
case .success:
completionHandler(.success(()), httpResponse)
case let .failure(error):
completionHandler(.failure(error), httpResponse)
}
}
}
/// Deletes a message.
/// - Parameters:
/// - messageId : The message id.
/// - options: Delete chat message options
/// - completionHandler: A completion handler that receives a status code on success.
public func delete(
message messageId: String,
options: DeleteChatMessageOptions? = nil,
completionHandler: @escaping HTTPResultHandler<Void>
) {
service
.deleteChatMessage(
chatThreadId: threadId,
chatMessageId: messageId,
withOptions: options
) { result, httpResponse in
switch result {
case .success:
completionHandler(.success(()), httpResponse)
case let .failure(error):
completionHandler(.failure(error), httpResponse)
}
}
}
/// Gets a list of messages from a ChatThread.
/// - Parameters:
/// - options: List messages options.
/// - completionHandler: A completion handler that receives the list of messages on success.
public func listMessages(
withOptions options: ListChatMessagesOptions? = nil,
completionHandler: @escaping HTTPResultHandler<PagedCollection<ChatMessage>>
) {
service.listChatMessages(chatThreadId: threadId, withOptions: options) { result, httpResponse in
switch result {
case .success:
// TODO: github.com/Azure/azure-sdk-for-ios/issues/644
// Construct a new PagedCollection of type ChatMessage
do {
let messages = try self.createPagedCollection(
from: httpResponse?.data,
withRequest: httpResponse?.httpRequest,
of: ChatMessage.self
)
completionHandler(.success(messages), httpResponse)
} catch {
let azureError = AzureError.client(error.localizedDescription, error)
completionHandler(.failure(azureError), httpResponse)
}
case let .failure(error):
completionHandler(.failure(error), httpResponse)
}
}
}
/// Adds participants to a ChatThread. If the participants already exist, no change occurs.
/// - Parameters:
/// - participants : An array of chat participants to add.
/// - options: Add chat participants options.
/// - completionHandler: A completion handler that receives a status code on success.
public func add(
participants: [ChatParticipant],
withOptions options: AddChatParticipantsOptions? = nil,
completionHandler: @escaping HTTPResultHandler<AddChatParticipantsResult>
) {
// Convert to ChatParticipantInternal for request
let participantsInternal: [ChatParticipantInternal]
do {
participantsInternal = try convert(participants: participants)
} catch {
completionHandler(
.failure(AzureError.client("Failed to convert participants to ChatParticipantInternal")),
nil
)
return
}
// Convert to AddChatParticipantsRequest for generated code
let addParticipantsRequest = AddChatParticipantsRequestInternal(
participants: participantsInternal
)
service.add(
chatParticipants: addParticipantsRequest,
chatThreadId: threadId,
withOptions: options
) { result, httpResponse in
switch result {
case let .success(addParticipantsResult):
completionHandler(.success(addParticipantsResult), httpResponse)
case let .failure(error):
completionHandler(.failure(error), httpResponse)
}
}
}
/// Removes a participant from the thread.
/// - Parameters:
/// - participantIdentifier : Identifier of the participant to remove.
/// - options: Remove participant options
/// - completionHandler: A completion handler that receives a status code on success.
public func remove(
participant participantIdentifier: CommunicationIdentifier,
withOptions options: RemoveChatParticipantOptions? = nil,
completionHandler: @escaping HTTPResultHandler<Void>
) {
do {
// Construct CommunicationIdentifierModel from participantId
let identifierModel = try IdentifierSerializer
.serialize(identifier: participantIdentifier)
service
.remove(
chatParticipant: identifierModel,
chatThreadId: threadId,
withOptions: options
) { result, httpResponse in
switch result {
case .success:
completionHandler(.success(()), httpResponse)
case let .failure(error):
completionHandler(.failure(error), httpResponse)
}
}
} catch {
// Return error from serializing the identifier
let azureError = AzureError.client("Failed to construct remove participant request.", error)
completionHandler(.failure(azureError), nil)
}
}
/// Gets the participants of the thread.
/// - Parameters:
/// - options: List chat participants options.
/// - completionHandler: A completion handler that receives the list of members on success.
public func listParticipants(
withOptions options: ListChatParticipantsOptions? = nil,
completionHandler: @escaping HTTPResultHandler<PagedCollection<ChatParticipant>>
) {
service.listChatParticipants(chatThreadId: threadId, withOptions: options) { result, httpResponse in
switch result {
case .success:
// TODO: https://github.com/Azure/azure-sdk-for-ios/issues/644
// Construct a new PagedCollection of type ChatParticipant
do {
let participants = try self.createPagedCollection(
from: httpResponse?.data,
withRequest: httpResponse?.httpRequest,
of: ChatParticipant.self
)
completionHandler(.success(participants), httpResponse)
} catch {
let azureError = AzureError.client(error.localizedDescription, error)
completionHandler(.failure(azureError), httpResponse)
}
case let .failure(error):
completionHandler(.failure(error), httpResponse)
}
}
}
}