From ebaad9f055a8704428ff7d5ca2035965c9c1aff1 Mon Sep 17 00:00:00 2001 From: JamesChenX Date: Sat, 3 Aug 2024 10:04:35 +0800 Subject: [PATCH] Migrate from PromiseKit to Swift async/await for turms-client-swift + Support sending requests simultaneously --- turms-client-swift/Package.swift | 2 +- .../Driver/Service/BaseService.swift | 5 +- .../Driver/Service/ConnectionService.swift | 75 +- .../Driver/Service/DriverMessageService.swift | 117 +-- .../Driver/Service/HeartbeatService.swift | 70 +- .../TurmsClient/Driver/TurmsDriver.swift | 46 +- .../Model/Proto/model/common/value.pb.swift | 14 +- .../Model/ResponseStatusCode.swift | 1 + .../Service/ConferenceService.swift | 127 ++-- .../Service/ConversationService.swift | 141 ++-- .../TurmsClient/Service/GroupService.swift | 589 +++++++-------- .../TurmsClient/Service/MessageService.swift | 207 +++--- .../Service/NotificationService.swift | 2 +- .../TurmsClient/Service/StorageService.swift | 674 ++++++++---------- .../TurmsClient/Service/UserService.swift | 507 ++++++------- .../Transport/MultipartFormDataRequest.swift | 2 +- .../TurmsClient/Transport/TcpClient.swift | 86 ++- .../Sources/TurmsClient/TurmsClient.swift | 5 +- .../Sources/TurmsClient/Util/Lock.swift | 20 + .../Driver/TurmsDriverTests.swift | 12 +- .../Helper/XCTestCase+Additions.swift | 43 +- .../Service/ConversationServiceTests.swift | 37 +- .../Service/GroupServiceTests.swift | 190 ++--- .../Service/MessageServiceTests.swift | 67 +- .../Service/StorageServiceTests.swift | 59 +- .../Service/UserServiceTests.swift | 121 ++-- 26 files changed, 1520 insertions(+), 1699 deletions(-) create mode 100644 turms-client-swift/Sources/TurmsClient/Util/Lock.swift diff --git a/turms-client-swift/Package.swift b/turms-client-swift/Package.swift index d2eabed962..89e440004f 100644 --- a/turms-client-swift/Package.swift +++ b/turms-client-swift/Package.swift @@ -27,7 +27,7 @@ let package = Package( targets: [ .target( name: "TurmsClient", - dependencies: ["PromiseKit", "SwiftProtobuf"] + dependencies: ["SwiftProtobuf"] ), .testTarget( name: "TurmsClientTests", diff --git a/turms-client-swift/Sources/TurmsClient/Driver/Service/BaseService.swift b/turms-client-swift/Sources/TurmsClient/Driver/Service/BaseService.swift index eb56116300..57c05c7242 100644 --- a/turms-client-swift/Sources/TurmsClient/Driver/Service/BaseService.swift +++ b/turms-client-swift/Sources/TurmsClient/Driver/Service/BaseService.swift @@ -1,5 +1,4 @@ import Foundation -import PromiseKit public class BaseService { let stateStore: StateStore @@ -8,9 +7,7 @@ public class BaseService { self.stateStore = stateStore } - func close() -> Promise { - return Promise.value(()) - } + func close() async {} func onDisconnected(_: Error? = nil) {} } diff --git a/turms-client-swift/Sources/TurmsClient/Driver/Service/ConnectionService.swift b/turms-client-swift/Sources/TurmsClient/Driver/Service/ConnectionService.swift index 9a485c5366..193d5b066a 100755 --- a/turms-client-swift/Sources/TurmsClient/Driver/Service/ConnectionService.swift +++ b/turms-client-swift/Sources/TurmsClient/Driver/Service/ConnectionService.swift @@ -1,5 +1,4 @@ import Foundation -import PromiseKit private class MessageDecoder { private static let maxReadBufferCapacity = 8 * 1024 * 1024 @@ -73,12 +72,13 @@ public class ConnectionService: BaseService { private let initialPort: UInt16 private let initialConnectTimeout: TimeInterval - private var disconnectPromises: [Resolver] = [] + private var disconnectContinuations: [UnsafeContinuation] = [] private var onConnectedListeners: [() -> Void] = [] private var onDisconnectedListeners: [(Error?) -> Void] = [] private var messageListeners: [(Data) -> Void] = [] + private let lock = Lock() private let decoder = MessageDecoder() init(stateStore: StateStore, host: String? = nil, port: UInt16? = nil, connectTimeout: TimeInterval? = nil) { @@ -89,7 +89,7 @@ public class ConnectionService: BaseService { } private func resetStates() { - fulfillDisconnectPromises() + fulfillDisconnectContinuations() } // Listeners @@ -124,48 +124,43 @@ public class ConnectionService: BaseService { } } - private func fulfillDisconnectPromises() { - repeat { - disconnectPromises.popLast()?.fulfill(()) - } while !disconnectPromises.isEmpty + private func fulfillDisconnectContinuations() { + lock.locked { + repeat { + disconnectContinuations.popLast()?.resume() + } while !disconnectContinuations.isEmpty + } } // Connection - public func connect(host: String? = nil, port: UInt16? = nil, connectTimeout _: TimeInterval? = nil, useTls: Bool? = false, certificatePinning: CertificatePinning? = nil) -> Promise { - return Promise { seal in - if stateStore.isConnected { - seal.reject(ResponseError(code: .clientSessionAlreadyEstablished)) - return - } - resetStates() - let tcp = TcpClient(onClosed: { [weak self] error in - self?.onSocketClosed(error) - }, onDataReceived: { [weak self] data in - guard let s = self else { return } - let messages = try s.decoder.decodeMessages(data) - for message in messages { - s.notifyMessageListeners(message) - } - }) - tcp.connect(host: host ?? initialHost, port: port ?? initialPort, useTls: useTls ?? false, certificatePinning: certificatePinning) - .done { [weak self] in - self?.onSocketOpened() - seal.fulfill_() - }.catch { error in - seal.reject(error) - } - stateStore.tcp = tcp + public func connect(host: String? = nil, port: UInt16? = nil, connectTimeout _: TimeInterval? = nil, useTls: Bool? = false, certificatePinning: CertificatePinning? = nil) async throws { + if stateStore.isConnected { + throw ResponseError(code: .clientSessionAlreadyEstablished) } + resetStates() + let tcp = TcpClient(onClosed: { [weak self] error in + self?.onSocketClosed(error) + }, onDataReceived: { [weak self] data in + guard let s = self else { return } + let messages = try s.decoder.decodeMessages(data) + for message in messages { + s.notifyMessageListeners(message) + } + }) + try await tcp.connect(host: host ?? initialHost, port: port ?? initialPort, useTls: useTls ?? false, certificatePinning: certificatePinning) + onSocketOpened() + stateStore.tcp = tcp } - public func disconnect() -> Promise { - return Promise { seal in + public func disconnect() async { + await withUnsafeContinuation { continuation in if !stateStore.isConnected { - seal.fulfill(()) - return + return continuation.resume() + } + lock.locked { + disconnectContinuations.append(continuation) } - disconnectPromises.append(seal) stateStore.tcp!.close() } } @@ -180,13 +175,13 @@ public class ConnectionService: BaseService { private func onSocketClosed(_ error: Error?) { decoder.clear() stateStore.isConnected = false - fulfillDisconnectPromises() + fulfillDisconnectContinuations() notifyOnDisconnectedListeners(error) } // Base methods - override func close() -> Promise { - return disconnect() + override func close() async { + return await disconnect() } -} +} \ No newline at end of file diff --git a/turms-client-swift/Sources/TurmsClient/Driver/Service/DriverMessageService.swift b/turms-client-swift/Sources/TurmsClient/Driver/Service/DriverMessageService.swift index 3b6aee764a..aab426913e 100755 --- a/turms-client-swift/Sources/TurmsClient/Driver/Service/DriverMessageService.swift +++ b/turms-client-swift/Sources/TurmsClient/Driver/Service/DriverMessageService.swift @@ -1,12 +1,22 @@ import Foundation -import PromiseKit + +private class RequestContext { + let continuation: UnsafeContinuation + let timeoutTask: Task? + + init(continuation: UnsafeContinuation, timeoutTask: Task?) { + self.continuation = continuation + self.timeoutTask = timeoutTask + } +} class DriverMessageService: BaseService { private let requestTimeout: TimeInterval private let minRequestInterval: TimeInterval private var notificationListeners: [(TurmsNotification) -> Void] = [] - private var requestMap: [Int64: Resolver] = [:] + private var requestIdToContext: [Int64: RequestContext] = [:] private var lastRequestDate = Date(timeIntervalSince1970: 0) + private let requestLock = Lock() init(stateStore: StateStore, requestTimeout: TimeInterval? = nil, minRequestInterval: TimeInterval? = nil) { self.requestTimeout = requestTimeout ?? 60 @@ -28,43 +38,62 @@ class DriverMessageService: BaseService { // Request and notification - func sendRequest(_ populator: (inout TurmsRequest) -> Void) -> Promise { + func sendRequest(_ populator: (inout TurmsRequest) -> Void) async throws -> TurmsNotification { var request = TurmsRequest() populator(&request) - return sendRequest(&request) + return try await sendRequest(&request) } - func sendRequest(_ request: inout TurmsRequest) -> Promise { - return Promise { seal in + func sendRequest(_ request: inout TurmsRequest) async throws -> TurmsNotification { + return try await withUnsafeThrowingContinuation { continuation in if case .createSessionRequest = request.kind { if stateStore.isSessionOpen { - return seal.reject(ResponseError(code: .clientSessionAlreadyEstablished)) + return continuation.resume(throwing: ResponseError(code: .clientSessionAlreadyEstablished)) } } else if !stateStore.isConnected || !stateStore.isSessionOpen { - return seal.reject(ResponseError(code: .clientSessionHasBeenClosed)) + return continuation.resume(throwing: ResponseError(code: .clientSessionHasBeenClosed)) + } + guard let tcp = stateStore.tcp else { + return continuation.resume(throwing: ResponseError(code: .clientSessionHasBeenClosed)) } let now = Date() let difference = now.timeIntervalSince1970 - lastRequestDate.timeIntervalSince1970 let isFrequent = minRequestInterval > 0 && difference <= minRequestInterval if isFrequent { - return seal.reject(ResponseError(code: .clientRequestsTooFrequent)) + return continuation.resume(throwing: ResponseError(code: .clientRequestsTooFrequent)) } - request.requestID = generateRandomId() - if requestTimeout > 0 { - after(.seconds(Int(requestTimeout))).done { - seal.reject(ResponseError(code: .requestTimeout)) + requestLock.locked { + let requestId = generateRandomId() + request.requestID = requestId + let data: Data + do { + data = try request.serializedData() + } catch { + return continuation.resume(throwing: ResponseError(code: .invalidRequest, reason: "Failed to serialize the request: \(request)", cause: error)) + } + var timeoutTask: Task? + if requestTimeout > 0 { + timeoutTask = Task { + do { + try await Task.sleep(nanoseconds: UInt64(requestTimeout * 1_000_000_000)) + requestLock.locked { + requestIdToContext.removeValue(forKey: requestId)?.continuation.resume(throwing: ResponseError(code: .requestTimeout)) + } + } catch {} + } + } + requestIdToContext.updateValue(RequestContext(continuation: continuation, timeoutTask: timeoutTask), forKey: request.requestID) + stateStore.lastRequestDate = now + Task { + do { + try await tcp.writeVarIntLengthAndBytes(data) + } catch { + requestLock.locked { + requestIdToContext.removeValue(forKey: requestId)?.continuation.resume(throwing: ResponseError(code: .networkError, reason: "Failed to write", cause: error)) + } + } } } - let data: Data - do { - data = try request.serializedData() - } catch { - seal.reject(ResponseError(code: .invalidRequest, reason: "Failed to serialize the request: \(request)", cause: error)) - return - } - requestMap.updateValue(seal, forKey: request.requestID) - stateStore.lastRequestDate = now - stateStore.tcp!.writeVarIntLengthAndBytes(data) } } @@ -72,20 +101,25 @@ class DriverMessageService: BaseService { let isResponse = !notification.hasRelayedRequest && notification.hasRequestID if isResponse { let requestId = notification.requestID - let handler = requestMap[requestId] - if notification.hasCode { - let code = Int(notification.code) - if ResponseStatusCode.isSuccessCode(code) { - handler?.fulfill(notification) - } else { - if notification.hasReason { - handler?.reject(ResponseError(code: code, reason: notification.reason)) + requestLock.locked { + if let context = requestIdToContext.removeValue(forKey: requestId) { + context.timeoutTask?.cancel() + let continuation = context.continuation + if notification.hasCode { + let code = Int(notification.code) + if ResponseStatusCode.isSuccessCode(code) { + continuation.resume(returning: notification) + } else { + if notification.hasReason { + continuation.resume(throwing: ResponseError(code: code, reason: notification.reason)) + } else { + continuation.resume(throwing: ResponseError(code: code)) + } + } } else { - handler?.reject(ResponseError(code: code)) + continuation.resume(throwing: ResponseError(code: ResponseStatusCode.invalidNotification, reason: "The code is missing")) } } - } else { - handler?.reject(ResponseError(code: ResponseStatusCode.invalidNotification, reason: "The code is missing")) } } notifyNotificationListener(notification) @@ -95,22 +129,23 @@ class DriverMessageService: BaseService { var id: Int64 repeat { id = Int64.random(in: 1 ... Int64.max) - } while requestMap.keys.contains(id) + } while requestIdToContext.keys.contains(id) return id } private func rejectRequests(_ e: ResponseError) { - repeat { - requestMap.popFirst()?.value.reject(e) - } while !requestMap.isEmpty + requestLock.locked { + repeat { + requestIdToContext.popFirst()?.value.continuation.resume(throwing: e) + } while !requestIdToContext.isEmpty + } } - override func close() -> Promise { + override func close() async { onDisconnected() - return Promise.value(()) } override func onDisconnected(_ error: Error? = nil) { rejectRequests(ResponseError(code: .clientSessionHasBeenClosed, cause: error)) } -} +} \ No newline at end of file diff --git a/turms-client-swift/Sources/TurmsClient/Driver/Service/HeartbeatService.swift b/turms-client-swift/Sources/TurmsClient/Driver/Service/HeartbeatService.swift index 7d3b4e4cb0..de0fc0d561 100755 --- a/turms-client-swift/Sources/TurmsClient/Driver/Service/HeartbeatService.swift +++ b/turms-client-swift/Sources/TurmsClient/Driver/Service/HeartbeatService.swift @@ -1,17 +1,16 @@ import Foundation -import PromiseKit class HeartbeatService: BaseService { private static let HEARTBEAT_FAILURE_REQUEST_ID: Int64 = -100 private static let HEARTBEAT_REQUEST = Data([0]) - private let createQueue = DispatchQueue(label: "im.turms.turmsclient.heartbeatservice.createqueue") + private let lock = Lock() private let heartbeatInterval: TimeInterval private let heartbeatTimerInterval: TimeInterval private var lastHeartbeatRequestDate: TimeInterval = 0 private var heartbeatTimer: Timer? - private var heartbeatPromises: [Resolver] = [] + private var heartbeatContinuations: [UnsafeContinuation] = [] init(stateStore: StateStore, heartbeatInterval: TimeInterval? = nil) { self.heartbeatInterval = heartbeatInterval ?? 120 @@ -24,7 +23,7 @@ class HeartbeatService: BaseService { } func start() { - createQueue.sync { + lock.locked { if isRunning { return } @@ -42,59 +41,72 @@ class HeartbeatService: BaseService { heartbeatTimer?.invalidate() } - func send() -> Promise { - return Promise { seal in + func send() async throws { + return try await withUnsafeThrowingContinuation { continuation in if !stateStore.isConnected || !stateStore.isSessionOpen { - seal.reject(ResponseError(code: .clientSessionHasBeenClosed)) - return + return continuation.resume(throwing: ResponseError(code: .clientSessionHasBeenClosed)) } - stateStore.tcp!.write(HeartbeatService.HEARTBEAT_REQUEST) { error in - if let error = error { - seal.reject(error) - return + Task { + guard let tcp = stateStore.tcp else { + return continuation.resume(throwing: ResponseError(code: .clientSessionHasBeenClosed)) + } + do { + try await tcp.write(HeartbeatService.HEARTBEAT_REQUEST) + } catch { + return continuation.resume(throwing: error) + } + lock.locked { + self.heartbeatContinuations.append(continuation) } - self.heartbeatPromises.append(seal) } } } - func fulfillHeartbeatPromises() { - repeat { - heartbeatPromises.popLast()?.fulfill(()) - } while !heartbeatPromises.isEmpty + func fulfillHeartbeatContinuations() { + lock.locked { + repeat { + heartbeatContinuations.popLast()?.resume() + } while !heartbeatContinuations.isEmpty + } } - func rejectHeartbeatPromisesIfFail(_ notification: TurmsNotification) -> Bool { + func rejectHeartbeatContinuationsIfFail(_ notification: TurmsNotification) -> Bool { if notification.hasRequestID, notification.requestID == HeartbeatService.HEARTBEAT_FAILURE_REQUEST_ID { - rejectHeartbeatPromises(ResponseError(notification)) + rejectHeartbeatContinuations(ResponseError(notification)) return true } return false } - func rejectHeartbeatPromises(_ error: Error) { - repeat { - heartbeatPromises.popLast()?.reject(error) - } while !heartbeatPromises.isEmpty + func rejectHeartbeatContinuations(_ error: Error) { + lock.locked { + repeat { + heartbeatContinuations.popLast()?.resume(throwing: error) + } while !heartbeatContinuations.isEmpty + } } - @objc func checkAndSendHeartbeat() { + @objc func checkAndSendHeartbeat() async throws { let now = Date().timeIntervalSince1970 let difference = min(now - stateStore.lastRequestDate.timeIntervalSince1970, now - lastHeartbeatRequestDate) if difference > heartbeatInterval { - send() - lastHeartbeatRequestDate = now + do { + try await send() + } catch { + // TODO: log + return + } + lastHeartbeatRequestDate = max(lastHeartbeatRequestDate, now) } } - override func close() -> Promise { + override func close() async { onDisconnected() - return Promise.value(()) } override func onDisconnected(_ error: Error? = nil) { stop() - rejectHeartbeatPromises(ResponseError(code: .clientSessionHasBeenClosed, cause: error)) + rejectHeartbeatContinuations(ResponseError(code: .clientSessionHasBeenClosed, cause: error)) } } diff --git a/turms-client-swift/Sources/TurmsClient/Driver/TurmsDriver.swift b/turms-client-swift/Sources/TurmsClient/Driver/TurmsDriver.swift index 90f49a6007..5b3ab70585 100755 --- a/turms-client-swift/Sources/TurmsClient/Driver/TurmsDriver.swift +++ b/turms-client-swift/Sources/TurmsClient/Driver/TurmsDriver.swift @@ -1,5 +1,4 @@ import Foundation -import PromiseKit public class TurmsDriver { let stateStore: StateStore @@ -29,9 +28,8 @@ public class TurmsDriver { } } - public func close() -> Promise { - return when(resolved: connectionService.close(), heartbeatService.close(), messageService.close()) - .asVoid() + public func close() async { + _ = await [connectionService.close(), heartbeatService.close(), messageService.close()] } // Heartbeat Service @@ -44,8 +42,8 @@ public class TurmsDriver { heartbeatService.stop() } - public func sendHeartbeat() -> Promise { - return heartbeatService.send() + public func sendHeartbeat() async throws { + try await heartbeatService.send() } public var isHeartbeatRunning: Bool { @@ -54,12 +52,12 @@ public class TurmsDriver { // Connection Service - public func connect(host: String? = nil, port: UInt16? = nil, connectTimeout: TimeInterval? = nil, certificatePinning: CertificatePinning? = nil) -> Promise { - return connectionService.connect(host: host, port: port, connectTimeout: connectTimeout, certificatePinning: certificatePinning) + public func connect(host: String? = nil, port: UInt16? = nil, connectTimeout: TimeInterval? = nil, certificatePinning: CertificatePinning? = nil) async throws { + try await connectionService.connect(host: host, port: port, connectTimeout: connectTimeout, certificatePinning: certificatePinning) } - public func disconnect() -> Promise { - return connectionService.disconnect() + public func disconnect() async { + await connectionService.disconnect() } public var isConnected: Bool { @@ -78,18 +76,12 @@ public class TurmsDriver { // Message Service - public func send(_ populator: (inout TurmsRequest) throws -> Void) -> Promise { + public func send(_ populator: (inout TurmsRequest) throws -> Void) async throws -> TurmsNotification { var request = TurmsRequest() - do { - try populator(&request) - } catch { - return Promise(error: error) - } - let notification = messageService.sendRequest(&request) + try populator(&request) + let notification = try await messageService.sendRequest(&request) if case .createSessionRequest = request.kind { - notification.done { _ in - self.heartbeatService.start() - } + heartbeatService.start() } return notification } @@ -108,24 +100,26 @@ public class TurmsDriver { private func onMessage(_ message: Data) { if message.isEmpty { - heartbeatService.fulfillHeartbeatPromises() + heartbeatService.fulfillHeartbeatContinuations() } else { var notification: TurmsNotification do { - notification = try TurmsNotification(serializedData: message) + notification = try TurmsNotification(serializedBytes: message) } catch { Logger.error("Failed to parse TurmsNotification: %@", String(describing: error)) return } - if heartbeatService.rejectHeartbeatPromisesIfFail(notification) { + if heartbeatService.rejectHeartbeatContinuationsIfFail(notification) { return } if notification.hasCloseStatus { stateStore.isSessionOpen = false messageService.didReceiveNotification(notification) - // We must close the connection after finishing handling the notification - // to ensure notification handlers will always be triggered before connection close handlers. - connectionService.disconnect() + Task { + // We must close the connection after finishing handling the notification + // to ensure notification handlers will always be triggered before connection close handlers. + await connectionService.disconnect() + } return } if notification.hasData, case .userSession = notification.data.kind! { diff --git a/turms-client-swift/Sources/TurmsClient/Model/Proto/model/common/value.pb.swift b/turms-client-swift/Sources/TurmsClient/Model/Proto/model/common/value.pb.swift index 2bb9341040..8675142bca 100644 --- a/turms-client-swift/Sources/TurmsClient/Model/Proto/model/common/value.pb.swift +++ b/turms-client-swift/Sources/TurmsClient/Model/Proto/model/common/value.pb.swift @@ -170,7 +170,7 @@ extension Value: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase case 1: try { var v: Int32? try decoder.decodeSingularInt32Field(value: &v) - if let v = v { + if let v { if self.kind != nil { try decoder.handleConflictingOneOf() } self.kind = .int32Value(v) } @@ -178,7 +178,7 @@ extension Value: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase case 2: try { var v: Int64? try decoder.decodeSingularInt64Field(value: &v) - if let v = v { + if let v { if self.kind != nil { try decoder.handleConflictingOneOf() } self.kind = .int64Value(v) } @@ -186,7 +186,7 @@ extension Value: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase case 3: try { var v: Float? try decoder.decodeSingularFloatField(value: &v) - if let v = v { + if let v { if self.kind != nil { try decoder.handleConflictingOneOf() } self.kind = .floatValue(v) } @@ -194,7 +194,7 @@ extension Value: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase case 4: try { var v: Double? try decoder.decodeSingularDoubleField(value: &v) - if let v = v { + if let v { if self.kind != nil { try decoder.handleConflictingOneOf() } self.kind = .doubleValue(v) } @@ -202,7 +202,7 @@ extension Value: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase case 5: try { var v: Bool? try decoder.decodeSingularBoolField(value: &v) - if let v = v { + if let v { if self.kind != nil { try decoder.handleConflictingOneOf() } self.kind = .boolValue(v) } @@ -210,7 +210,7 @@ extension Value: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase case 6: try { var v: Data? try decoder.decodeSingularBytesField(value: &v) - if let v = v { + if let v { if self.kind != nil { try decoder.handleConflictingOneOf() } self.kind = .bytesValue(v) } @@ -218,7 +218,7 @@ extension Value: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase case 7: try { var v: String? try decoder.decodeSingularStringField(value: &v) - if let v = v { + if let v { if self.kind != nil { try decoder.handleConflictingOneOf() } self.kind = .stringValue(v) } diff --git a/turms-client-swift/Sources/TurmsClient/Model/ResponseStatusCode.swift b/turms-client-swift/Sources/TurmsClient/Model/ResponseStatusCode.swift index 46cd408f45..19f6b926ee 100644 --- a/turms-client-swift/Sources/TurmsClient/Model/ResponseStatusCode.swift +++ b/turms-client-swift/Sources/TurmsClient/Model/ResponseStatusCode.swift @@ -10,6 +10,7 @@ public enum ResponseStatusCode: Int { // Client - Common case connectTimeout = 1 case dataNotFound = 10 + case networkError = 80 case httpError = 90 case httpNotSuccessfulResponse diff --git a/turms-client-swift/Sources/TurmsClient/Service/ConferenceService.swift b/turms-client-swift/Sources/TurmsClient/Service/ConferenceService.swift index 4f08215c31..9dca7793be 100644 --- a/turms-client-swift/Sources/TurmsClient/Service/ConferenceService.swift +++ b/turms-client-swift/Sources/TurmsClient/Service/ConferenceService.swift @@ -1,8 +1,7 @@ import Foundation -import PromiseKit public class ConferenceService { - private weak var turmsClient: TurmsClient! + private unowned var turmsClient: TurmsClient init(_ turmsClient: TurmsClient) { self.turmsClient = turmsClient @@ -60,34 +59,31 @@ public class ConferenceService { name: String? = nil, intro: String? = nil, password: String? = nil, - startDate: Date? = nil) -> Promise> + startDate: Date? = nil) async throws -> Response { - return turmsClient.driver + return try (await turmsClient.driver .send { $0.createMeetingRequest = .with { - if let v = userId { - $0.userID = v + if let userId { + $0.userID = userId } - if let v = groupId { - $0.groupID = v + if let groupId { + $0.groupID = groupId } - if let v = name { - $0.name = v + if let name { + $0.name = name } - if let v = intro { - $0.intro = v + if let intro { + $0.intro = intro } - if let v = password { - $0.password = v + if let password { + $0.password = password } - if let v = startDate { - $0.startDate = v.toMillis() + if let startDate { + $0.startDate = startDate.toMillis() } } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Cancel a meeting. @@ -108,16 +104,13 @@ public class ConferenceService { /// /// - Throws: ``ResponseError`` if an error occurs. /// * If the server hasn't implemented the feature, throws ``ResponseError`` with the code ``ResponseStatusCode/conferenceNotImplemented``. - public func cancelMeeting(_ meetingId: Int64) -> Promise> { - return turmsClient.driver + public func cancelMeeting(_ meetingId: Int64) async throws -> Response { + return try (await turmsClient.driver .send { $0.deleteMeetingRequest = .with { $0.id = meetingId } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Update a meeting. @@ -149,26 +142,23 @@ public class ConferenceService { public func updateMeeting(_ meetingId: Int64, name: String? = nil, intro: String? = nil, - password: String? = nil) -> Promise> + password: String? = nil) async throws -> Response { - return turmsClient.driver + return try (await turmsClient.driver .send { $0.updateMeetingRequest = .with { $0.id = meetingId - if let v = name { - $0.name = v + if let name { + $0.name = name } - if let v = intro { - $0.intro = v + if let intro { + $0.intro = intro } - if let v = password { - $0.password = v + if let password { + $0.password = password } } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Find meetings. @@ -191,42 +181,39 @@ public class ConferenceService { creationDateStart: Date? = nil, creationDateEnd: Date? = nil, skip: Int32? = nil, - limit: Int32? = nil) -> Promise> + limit: Int32? = nil) async throws -> Response<[Meeting]> { - return turmsClient.driver + return try (await turmsClient.driver .send { $0.queryMeetingsRequest = .with { - if let v = meetingIds { - $0.ids = v + if let meetingIds { + $0.ids = meetingIds } - if let v = creatorIds { - $0.creatorIds = v + if let creatorIds { + $0.creatorIds = creatorIds } - if let v = userIds { - $0.userIds = v + if let userIds { + $0.userIds = userIds } - if let v = groupIds { - $0.groupIds = v + if let groupIds { + $0.groupIds = groupIds } - if let v = creationDateStart { - $0.creationDateStart = v.toMillis() + if let creationDateStart { + $0.creationDateStart = creationDateStart.toMillis() } - if let v = creationDateEnd { - $0.creationDateEnd = v.toMillis() + if let creationDateEnd { + $0.creationDateEnd = creationDateEnd.toMillis() } - if let v = skip { - $0.skip = v + if let skip { + $0.skip = skip } - if let v = limit { - $0.limit = v + if let limit { + $0.limit = limit } } - } - .map { - try $0.toResponse { - try $0.meetings.meetings - } - } + }).toResponse { + $0.meetings.meetings + } } /// Accept a meeting invitation. @@ -251,20 +238,18 @@ public class ConferenceService { /// /// - Throws: ``ResponseError`` if an error occurs. /// * If the server hasn't implemented the feature, throws ``ResponseError`` with the code ``ResponseStatusCode/conferenceNotImplemented``. - public func acceptMeetingInvitation(_ meetingId: Int64, password: String? = nil) -> Promise> { - return turmsClient.driver + public func acceptMeetingInvitation(_ meetingId: Int64, password: String? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.updateMeetingInvitationRequest = .with { $0.meetingID = meetingId $0.responseAction = .accept - if let v = password { - $0.password = v + if let password { + $0.password = password } } - } - .map { try $0.toResponse { - try $0.string - } - } + }).toResponse { + $0.string + } } } diff --git a/turms-client-swift/Sources/TurmsClient/Service/ConversationService.swift b/turms-client-swift/Sources/TurmsClient/Service/ConversationService.swift index 84df09e9e0..beaca9c5cb 100644 --- a/turms-client-swift/Sources/TurmsClient/Service/ConversationService.swift +++ b/turms-client-swift/Sources/TurmsClient/Service/ConversationService.swift @@ -1,8 +1,7 @@ import Foundation -import PromiseKit public class ConversationService { - private weak var turmsClient: TurmsClient! + private unowned var turmsClient: TurmsClient init(_ turmsClient: TurmsClient) { self.turmsClient = turmsClient @@ -30,21 +29,18 @@ public class ConversationService { /// these conversations will be filtered out on the server, and no error will be thrown. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryPrivateConversations(_ userIds: [Int64]) -> Promise> { + public func queryPrivateConversations(_ userIds: [Int64]) async throws -> Response<[PrivateConversation]> { if userIds.isEmpty { - return Promise.value(Response<[PrivateConversation]>.emptyArray()) + return Response<[PrivateConversation]>.emptyArray() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.queryConversationsRequest = .with { $0.userIds = userIds } - } - .map { - try $0.toResponse { - $0.conversations.privateConversations - } - } + }).toResponse { + $0.conversations.privateConversations + } } /// Find group conversations in which the logged-in user is a member. @@ -69,21 +65,18 @@ public class ConversationService { /// these conversations will be filtered out on the server, and no error will be thrown. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryGroupConversations(_ groupIds: [Int64]) -> Promise> { + public func queryGroupConversations(_ groupIds: [Int64]) async throws -> Response<[GroupConversation]> { if groupIds.isEmpty { - return Promise.value(Response<[GroupConversation]>.emptyArray()) + return Response<[GroupConversation]>.emptyArray() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.queryConversationsRequest = .with { $0.groupIds = groupIds } - } - .map { - try $0.toResponse { - $0.conversations.groupConversations - } - } + }).toResponse { + $0.conversations.groupConversations + } } /// Update the read date of the target private conversation. @@ -110,17 +103,14 @@ public class ConversationService { /// If null, the current time is used. /// /// - Throws: ``ResponseError`` if an error occurs. - public func updatePrivateConversationReadDate(_ userId: Int64, readDate: Date = Date()) -> Promise> { - return turmsClient.driver + public func updatePrivateConversationReadDate(_ userId: Int64, readDate: Date = Date()) async throws -> Response { + return try (await turmsClient.driver .send { $0.updateConversationRequest = .with { $0.userID = userId $0.readDate = readDate.toMillis() } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Update the read date of the target group conversation. @@ -150,17 +140,14 @@ public class ConversationService { /// If null, the current time is used. /// /// - Throws: ``ResponseError`` if an error occurs. - public func updateGroupConversationReadDate(_ groupId: Int64, readDate: Date = Date()) -> Promise> { - return turmsClient.driver + public func updateGroupConversationReadDate(_ groupId: Int64, readDate: Date = Date()) async throws -> Response { + return try (await turmsClient.driver .send { $0.updateConversationRequest = .with { $0.groupID = groupId $0.readDate = readDate.toMillis() } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Upsert private conversation settings, such as "sticky", "new message alert", etc. @@ -178,20 +165,17 @@ public class ConversationService { /// * If trying to update any existing immutable setting, throws ``ResponseError`` with the code ``ResponseStatusCode/illegalArgument`` /// * If trying to upsert an unknown setting and the server property `turms.service.conversation.settings.ignore-unknown-settings-on-upsert` is /// false (false by default), throws ``ResponseError`` with the code ``ResponseStatusCode/illegalArgument``. - public func upsertPrivateConversationSettings(_ userId: Int64, _ settings: [String: Value]) -> Promise> { + public func upsertPrivateConversationSettings(_ userId: Int64, _ settings: [String: Value]) async throws -> Response { if settings.isEmpty { - return Promise.value(Response.empty()) + return Response.empty() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.updateConversationSettingsRequest = .with { $0.userID = userId $0.settings = settings } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Upsert group conversation settings, such as "sticky", "new message alert", etc. @@ -209,20 +193,17 @@ public class ConversationService { /// * If trying to update any existing immutable setting, throws ``ResponseError`` with the code ``ResponseStatusCode/illegalArgument`` /// * If trying to upsert an unknown setting and the server property `turms.service.conversation.settings.ignore-unknown-settings-on-upsert` is /// false (false by default), throws ``ResponseError`` with the code ``ResponseStatusCode/illegalArgument``. - public func upsertGroupConversationSettings(_ groupId: Int64, _ settings: [String: Value]) -> Promise> { + public func upsertGroupConversationSettings(_ groupId: Int64, _ settings: [String: Value]) async throws -> Response { if settings.isEmpty { - return Promise.value(Response.empty()) + return Response.empty() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.updateConversationSettingsRequest = .with { $0.groupID = groupId $0.settings = settings } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Delete conversation settings. @@ -240,24 +221,21 @@ public class ConversationService { /// /// - Throws: ``ResponseError`` if an error occurs. /// * If trying to delete any non-deletable setting, throws ``ResponseError`` with the code ``ResponseStatusCode/illegalArgument``. - public func deleteConversationSettings(userIds: [Int64]? = nil, groupIds: [Int64]? = nil, names: [String]? = nil) -> Promise> { - return turmsClient.driver + public func deleteConversationSettings(userIds: [Int64]? = nil, groupIds: [Int64]? = nil, names: [String]? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.deleteConversationSettingsRequest = .with { - if let v = userIds { - $0.userIds = v + if let userIds { + $0.userIds = userIds } - if let v = groupIds { - $0.groupIds = v + if let groupIds { + $0.groupIds = groupIds } - if let v = names { - $0.names = v + if let names { + $0.names = names } } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Find conversation settings. @@ -270,29 +248,26 @@ public class ConversationService { /// The server will only return conversation settings if a setting has been updated after `lastUpdatedDate`. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryConversationSettings(userIds: [Int64]? = nil, groupIds: [Int64]? = nil, names: [String]? = nil, lastUpdatedDate: Date? = nil) -> Promise> { - return turmsClient.driver + public func queryConversationSettings(userIds: [Int64]? = nil, groupIds: [Int64]? = nil, names: [String]? = nil, lastUpdatedDate: Date? = nil) async throws -> Response<[ConversationSettings]> { + return try (await turmsClient.driver .send { $0.queryConversationSettingsRequest = .with { - if let v = userIds { - $0.userIds = v + if let userIds { + $0.userIds = userIds } - if let v = groupIds { - $0.groupIds = v + if let groupIds { + $0.groupIds = groupIds } - if let v = names { - $0.names = v + if let names { + $0.names = names } - if let v = lastUpdatedDate { - $0.lastUpdatedDateStart = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDateStart = lastUpdatedDate.toMillis() } } - } - .map { - try $0.toResponse { - $0.conversationSettingsList.conversationSettingsList - } - } + }).toResponse { + $0.conversationSettingsList.conversationSettingsList + } } /// Update the typing status of the target private conversation. @@ -311,17 +286,14 @@ public class ConversationService { /// - userId: The target user ID. /// /// - Throws: ``ResponseError`` if an error occurs. - public func updatePrivateConversationTypingStatus(_ userId: Int64) -> Promise> { - return turmsClient.driver + public func updatePrivateConversationTypingStatus(_ userId: Int64) async throws -> Response { + return try (await turmsClient.driver .send { $0.updateTypingStatusRequest = .with { $0.toID = userId $0.isGroupMessage = false } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Update the typing status of the target group conversation. @@ -340,16 +312,13 @@ public class ConversationService { /// - groupId: The target group ID. /// /// - Throws: ``ResponseError`` if an error occurs. - public func updateGroupConversationTypingStatus(_ groupId: Int64) -> Promise> { - return turmsClient.driver + public func updateGroupConversationTypingStatus(_ groupId: Int64) async throws -> Response { + return try (await turmsClient.driver .send { $0.updateTypingStatusRequest = .with { $0.toID = groupId $0.isGroupMessage = true } - } - .map { - try $0.toResponse() - } + }).toResponse() } } diff --git a/turms-client-swift/Sources/TurmsClient/Service/GroupService.swift b/turms-client-swift/Sources/TurmsClient/Service/GroupService.swift index 95c1444f4e..8e9e1d1d07 100755 --- a/turms-client-swift/Sources/TurmsClient/Service/GroupService.swift +++ b/turms-client-swift/Sources/TurmsClient/Service/GroupService.swift @@ -1,8 +1,7 @@ import Foundation -import PromiseKit public class GroupService { - private weak var turmsClient: TurmsClient! + private unowned var turmsClient: TurmsClient public init(_ turmsClient: TurmsClient) { self.turmsClient = turmsClient @@ -47,33 +46,30 @@ public class GroupService { minScore: Int32? = nil, muteEndDate: Date? = nil, typeId: Int64? = nil - ) -> Promise> { - return turmsClient.driver + ) async throws -> Response { + return try (await turmsClient.driver .send { $0.createGroupRequest = .with { $0.name = name - if let v = intro { - $0.intro = v + if let intro { + $0.intro = intro } - if let v = announcement { - $0.announcement = v + if let announcement { + $0.announcement = announcement } - if let v = minScore { - $0.minScore = v + if let minScore { + $0.minScore = minScore } - if let v = muteEndDate { - $0.muteEndDate = v.toMillis() + if let muteEndDate { + $0.muteEndDate = muteEndDate.toMillis() } - if let v = typeId { - $0.typeID = v + if let typeId { + $0.typeID = typeId } } - } - .map { - try $0.toResponse { - try $0.getLongOrThrow() - } - } + }).toResponse { + try $0.getLongOrThrow() + } } /// Delete the target group. @@ -91,16 +87,13 @@ public class GroupService { /// the server will send a delete group notification to all group members of the target group. /// /// - Throws: ``ResponseError`` if an error occurs. - public func deleteGroup(_ groupId: Int64) -> Promise> { - return turmsClient.driver + public func deleteGroup(_ groupId: Int64) async throws -> Response { + return try (await turmsClient.driver .send { $0.deleteGroupRequest = .with { $0.groupID = groupId } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Update the target group. @@ -197,7 +190,7 @@ public class GroupService { muteEndDate: Date? = nil, successorId: Int64? = nil, quitAfterTransfer: Bool? = nil - ) -> Promise> { + ) async throws -> Response { if Validator.areAllNil( name, intro, @@ -207,41 +200,38 @@ public class GroupService { muteEndDate, successorId ) { - return Promise.value(Response.empty()) + return Response.empty() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.updateGroupRequest = .with { $0.groupID = groupId - if let v = name { - $0.name = v + if let name { + $0.name = name } - if let v = intro { - $0.intro = v + if let intro { + $0.intro = intro } - if let v = announcement { - $0.announcement = v + if let announcement { + $0.announcement = announcement } - if let v = muteEndDate { - $0.muteEndDate = v.toMillis() + if let muteEndDate { + $0.muteEndDate = muteEndDate.toMillis() } - if let v = minScore { - $0.minScore = v + if let minScore { + $0.minScore = minScore } - if let v = typeId { - $0.typeID = v + if let typeId { + $0.typeID = typeId } - if let v = successorId { - $0.successorID = v + if let successorId { + $0.successorID = successorId } - if let v = quitAfterTransfer { - $0.quitAfterTransfer = v + if let quitAfterTransfer { + $0.quitAfterTransfer = quitAfterTransfer } } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Transfer the group ownership. @@ -268,18 +258,15 @@ public class GroupService { /// throws ``ResponseError`` with the code ``ResponseStatusCode/notGroupOwnerToTransferGroup``. /// /// - Throws: ``ResponseError`` if an error occurs. - public func transferOwnership(groupId: Int64, successorId: Int64, quitAfterTransfer: Bool = false) -> Promise> { - return turmsClient.driver + public func transferOwnership(groupId: Int64, successorId: Int64, quitAfterTransfer: Bool = false) async throws -> Response { + return try (await turmsClient.driver .send { $0.updateGroupRequest = .with { $0.groupID = groupId $0.successorID = successorId $0.quitAfterTransfer = quitAfterTransfer } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Mute the target group. @@ -303,17 +290,14 @@ public class GroupService { /// throws ``ResponseError`` with the code ``ResponseStatusCode/notGroupOwnerOrManagerToMuteGroupMember``. /// /// - Throws: ``ResponseError`` if an error occurs. - public func muteGroup(groupId: Int64, muteEndDate: Date) -> Promise> { - return turmsClient.driver + public func muteGroup(groupId: Int64, muteEndDate: Date) async throws -> Response { + return try (await turmsClient.driver .send { $0.updateGroupRequest = .with { $0.groupID = groupId $0.muteEndDate = muteEndDate.toMillis() } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Unmute the target group. @@ -334,8 +318,8 @@ public class GroupService { /// - groupId: The target group ID to find the group for updating. /// /// - Throws: ``ResponseError`` if an error occurs. - public func unmuteGroup(_ groupId: Int64) -> Promise> { - return muteGroup(groupId: groupId, muteEndDate: Date(timeIntervalSince1970: 0)) + public func unmuteGroup(_ groupId: Int64) async throws -> Response { + return try await muteGroup(groupId: groupId, muteEndDate: Date(timeIntervalSince1970: 0)) } /// Find groups. @@ -349,24 +333,21 @@ public class GroupService { /// - Returns: A list of groups. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryGroups(groupIds: Set, lastUpdatedDate: Date? = nil) -> Promise> { + public func queryGroups(groupIds: Set, lastUpdatedDate: Date? = nil) async throws -> Response<[Group]> { if groupIds.isEmpty { - return Promise.value(Response.emptyArray()) + return Response.emptyArray() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.queryGroupsRequest = .with { $0.groupIds = Array(groupIds) - if let v = lastUpdatedDate { - $0.lastUpdatedDate = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDate = lastUpdatedDate.toMillis() } } - } - .map { - try $0.toResponse { - $0.groupsWithVersion.groups - } - } + }).toResponse { + $0.groupsWithVersion.groups + } } /// Search for groups. @@ -384,31 +365,28 @@ public class GroupService { public func searchGroups(name: String, highlight: Bool = false, skip: Int32? = nil, - limit: Int32? = nil) -> Promise> + limit: Int32? = nil) async throws -> Response<[Group]> { if name.isEmpty { - return Promise.value(Response.emptyArray()) + return Response.emptyArray() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.queryGroupsRequest = .with { $0.name = name if highlight { $0.fieldsToHighlight = [1] } - if let v = skip { - $0.skip = v + if let skip { + $0.skip = skip } - if let v = limit { - $0.limit = v + if let limit { + $0.limit = limit } } - } - .map { - try $0.toResponse { - $0.groupsWithVersion.groups - } - } + }).toResponse { + $0.groupsWithVersion.groups + } } /// Find group IDs that the logged-in user has joined. @@ -422,20 +400,17 @@ public class GroupService { /// Note: The version can be used to update the last updated date on local. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryJoinedGroupIds(_ lastUpdatedDate: Date? = nil) -> Promise> { - return turmsClient.driver + public func queryJoinedGroupIds(_ lastUpdatedDate: Date? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.queryJoinedGroupIdsRequest = .with { - if let v = lastUpdatedDate { - $0.lastUpdatedDate = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDate = lastUpdatedDate.toMillis() } } - } - .map { - try $0.toResponse { - try $0.kind?.getKindData(LongsWithVersion.self) - } - } + }).toResponse { + try $0.kind?.getKindData(LongsWithVersion.self) + } } /// Find groups that the logged-in user has joined. @@ -446,20 +421,17 @@ public class GroupService { /// If null, all groups will be returned. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryJoinedGroupInfos(_ lastUpdatedDate: Date? = nil) -> Promise> { - return turmsClient.driver + public func queryJoinedGroupInfos(_ lastUpdatedDate: Date? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.queryJoinedGroupInfosRequest = .with { - if let v = lastUpdatedDate { - $0.lastUpdatedDate = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDate = lastUpdatedDate.toMillis() } } - } - .map { - try $0.toResponse { - try $0.kind?.getKindData(GroupsWithVersion.self) - } - } + }).toResponse { + try $0.kind?.getKindData(GroupsWithVersion.self) + } } /// Add group join/membership questions. @@ -482,11 +454,11 @@ public class GroupService { /// - Returns: New group questions IDs. /// /// - Throws: ``ResponseError`` if an error occurs. - public func addGroupJoinQuestions(groupId: Int64, questions: [NewGroupJoinQuestion]) -> Promise> { + public func addGroupJoinQuestions(groupId: Int64, questions: [NewGroupJoinQuestion]) async throws -> Response<[Int64]> { if questions.isEmpty { - return Promise.value(Response.emptyArray()) + return Response.emptyArray() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.createGroupJoinQuestionsRequest = try .with { $0.groupID = groupId @@ -510,12 +482,9 @@ public class GroupService { } } } - } - .map { - try $0.toResponse { - $0.longsWithVersion.longs - } - } + }).toResponse { + $0.longsWithVersion.longs + } } /// Delete group join/membership questions. @@ -529,20 +498,17 @@ public class GroupService { /// - questionIds: The group membership question IDs. /// /// - Throws: ``ResponseError`` if an error occurs. - public func deleteGroupJoinQuestions(groupId: Int64, questionIds: [Int64]) -> Promise> { + public func deleteGroupJoinQuestions(groupId: Int64, questionIds: [Int64]) async throws -> Response { if questionIds.isEmpty { - return Promise.value(Response.empty()) + return Response.empty() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.deleteGroupJoinQuestionsRequest = .with { $0.groupID = groupId $0.questionIds = questionIds } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Update group join/membership questions. @@ -561,28 +527,25 @@ public class GroupService { /// If null, the score will not be updated. /// /// - Throws: ``ResponseError`` if an error occurs. - public func updateGroupJoinQuestion(questionId: Int64, question: String? = nil, answers: [String]? = nil, score: Int32? = nil) -> Promise> { + public func updateGroupJoinQuestion(questionId: Int64, question: String? = nil, answers: [String]? = nil, score: Int32? = nil) async throws -> Response { if Validator.areAllNil(question, answers, score) { - return Promise.value(Response.empty()) + return Response.empty() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.updateGroupJoinQuestionRequest = .with { $0.questionID = questionId - if let v = question { - $0.question = v + if let question { + $0.question = question } - if let v = answers { - $0.answers = v + if let answers { + $0.answers = answers } - if let v = score { - $0.score = v + if let score { + $0.score = score } } - } - .map { - try $0.toResponse() - } + }).toResponse() } // Group Blocklist @@ -609,17 +572,14 @@ public class GroupService { /// - userId: The target user ID. /// /// - Throws: ``ResponseError`` if an error occurs. - public func blockUser(groupId: Int64, userId: Int64) -> Promise> { - return turmsClient.driver + public func blockUser(groupId: Int64, userId: Int64) async throws -> Response { + return try (await turmsClient.driver .send { $0.createGroupBlockedUserRequest = .with { $0.userID = userId $0.groupID = groupId } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Unblock a user in the group. @@ -641,17 +601,14 @@ public class GroupService { /// - userId: The target user ID. /// /// - Throws: ``ResponseError`` if an error occurs. - public func unblockUser(groupId: Int64, userId: Int64) -> Promise> { - return turmsClient.driver + public func unblockUser(groupId: Int64, userId: Int64) async throws -> Response { + return try (await turmsClient.driver .send { $0.deleteGroupBlockedUserRequest = .with { $0.groupID = groupId $0.userID = userId } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Find blocked user IDs. @@ -666,21 +623,18 @@ public class GroupService { public func queryBlockedUserIds( groupId: Int64, lastUpdatedDate: Date? = nil - ) -> Promise> { - return turmsClient.driver + ) async throws -> Response { + return try (await turmsClient.driver .send { $0.queryGroupBlockedUserIdsRequest = .with { $0.groupID = groupId - if let v = lastUpdatedDate { - $0.lastUpdatedDate = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDate = lastUpdatedDate.toMillis() } } - } - .map { - try $0.toResponse { - try $0.kind?.getKindData(LongsWithVersion.self) - } - } + }).toResponse { + try $0.kind?.getKindData(LongsWithVersion.self) + } } /// Find blocked user infos. @@ -695,21 +649,18 @@ public class GroupService { public func queryBlockedUserInfos( groupId: Int64, lastUpdatedDate: Date? = nil - ) -> Promise> { - return turmsClient.driver + ) async throws -> Response { + return try (await turmsClient.driver .send { $0.queryGroupBlockedUserInfosRequest = .with { $0.groupID = groupId - if let v = lastUpdatedDate { - $0.lastUpdatedDate = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDate = lastUpdatedDate.toMillis() } } - } - .map { - try $0.toResponse { - try $0.kind?.getKindData(UserInfosWithVersion.self) - } - } + }).toResponse { + try $0.kind?.getKindData(UserInfosWithVersion.self) + } } // Group Enrollment @@ -747,20 +698,17 @@ public class GroupService { /// - Returns: The invitation ID. /// /// - Throws: ``ResponseError`` if an error occurs. - public func createInvitation(groupId: Int64, inviteeId: Int64, content: String) -> Promise> { - return turmsClient.driver + public func createInvitation(groupId: Int64, inviteeId: Int64, content: String) async throws -> Response { + return try (await turmsClient.driver .send { $0.createGroupInvitationRequest = .with { $0.groupID = groupId $0.inviteeID = inviteeId $0.content = content } - } - .map { - try $0.toResponse { - try $0.getLongOrThrow() - } - } + }).toResponse { + try $0.getLongOrThrow() + } } /// Delete/Recall an invitation. @@ -790,16 +738,13 @@ public class GroupService { /// is true (true by default), the server will send a delete invitation notification to the target user actively. /// /// - Throws: ``ResponseError`` if an error occurs. - public func deleteInvitation(_ invitationId: Int64) -> Promise> { - return turmsClient.driver + public func deleteInvitation(_ invitationId: Int64) async throws -> Response { + return try (await turmsClient.driver .send { $0.deleteGroupInvitationRequest = .with { $0.invitationID = invitationId } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Reply to a group invitation. @@ -829,20 +774,17 @@ public class GroupService { /// - reason: The reason of the response. /// /// - Throws: ``ResponseError`` if an error occurs. - public func replyInvitation(invitationId: Int64, responseAction: ResponseAction, reason: String? = nil) -> Promise> { - return turmsClient.driver + public func replyInvitation(invitationId: Int64, responseAction: ResponseAction, reason: String? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.updateGroupInvitationRequest = .with { $0.invitationID = invitationId $0.responseAction = responseAction - if let v = reason { - $0.reason = v + if let reason { + $0.reason = reason } } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Find invitations. @@ -857,21 +799,18 @@ public class GroupService { /// Note: The version can be used to update the last updated date stored locally. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryInvitations(groupId: Int64, lastUpdatedDate: Date? = nil) -> Promise> { - return turmsClient.driver + public func queryInvitations(groupId: Int64, lastUpdatedDate: Date? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.queryGroupInvitationsRequest = .with { $0.groupID = groupId - if let v = lastUpdatedDate { - $0.lastUpdatedDate = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDate = lastUpdatedDate.toMillis() } } - } - .map { - try $0.toResponse { - try $0.kind?.getKindData(GroupInvitationsWithVersion.self) - } - } + }).toResponse { + try $0.kind?.getKindData(GroupInvitationsWithVersion.self) + } } /// Find invitations. @@ -886,21 +825,18 @@ public class GroupService { /// Note: The version can be used to update the last updated date stored locally. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryInvitations(areSentByMe: Bool, lastUpdatedDate: Date? = nil) -> Promise> { - return turmsClient.driver + public func queryInvitations(areSentByMe: Bool, lastUpdatedDate: Date? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.queryGroupInvitationsRequest = .with { $0.areSentByMe = areSentByMe - if let v = lastUpdatedDate { - $0.lastUpdatedDate = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDate = lastUpdatedDate.toMillis() } } - } - .map { - try $0.toResponse { - try $0.kind?.getKindData(GroupInvitationsWithVersion.self) - } - } + }).toResponse { + try $0.kind?.getKindData(GroupInvitationsWithVersion.self) + } } /// Create a group join/membership request. @@ -932,19 +868,16 @@ public class GroupService { /// - Returns: The request ID. /// /// - Throws: ``ResponseError`` if an error occurs. - public func createJoinRequest(groupId: Int64, content: String) -> Promise> { - return turmsClient.driver + public func createJoinRequest(groupId: Int64, content: String) async throws -> Response { + return try (await turmsClient.driver .send { $0.createGroupJoinRequestRequest = .with { $0.groupID = groupId $0.content = content } - } - .map { - try $0.toResponse { - try $0.getLongOrThrow() - } - } + }).toResponse { + try $0.getLongOrThrow() + } } /// Delete/Recall a group join/membership request. @@ -967,16 +900,13 @@ public class GroupService { /// is true (false by default), the server will send a delete join request notification to all group members of the target group actively. /// /// - Throws: ``ResponseError`` if an error occurs. - public func deleteJoinRequest(_ requestId: Int64) -> Promise> { - return turmsClient.driver + public func deleteJoinRequest(_ requestId: Int64) async throws -> Response { + return try (await turmsClient.driver .send { $0.deleteGroupJoinRequestRequest = .with { $0.requestID = requestId } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Reply a group join/membership request. @@ -1006,20 +936,17 @@ public class GroupService { /// - reason: The reason of the response. /// /// - Throws: ``ResponseError`` if an error occurs. - public func replyJoinRequest(requestId: Int64, responseAction: ResponseAction, reason: String? = nil) -> Promise> { - return turmsClient.driver + public func replyJoinRequest(requestId: Int64, responseAction: ResponseAction, reason: String? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.updateGroupJoinRequestRequest = .with { $0.requestID = requestId $0.responseAction = responseAction - if let v = reason { - $0.reason = v + if let reason { + $0.reason = reason } } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Find group join/membership requests. @@ -1034,21 +961,18 @@ public class GroupService { /// Note: The version can be used to update the last updated date stored locally. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryJoinRequests(groupId: Int64, lastUpdatedDate: Date? = nil) -> Promise> { - return turmsClient.driver + public func queryJoinRequests(groupId: Int64, lastUpdatedDate: Date? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.queryGroupJoinRequestsRequest = .with { $0.groupID = groupId - if let v = lastUpdatedDate { - $0.lastUpdatedDate = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDate = lastUpdatedDate.toMillis() } } - } - .map { - try $0.toResponse { - try $0.kind?.getKindData(GroupJoinRequestsWithVersion.self) - } - } + }).toResponse { + try $0.kind?.getKindData(GroupJoinRequestsWithVersion.self) + } } /// Find group join/membership requests sent by the logged-in user. @@ -1062,20 +986,17 @@ public class GroupService { /// Note: The version can be used to update the last updated date stored locally. /// /// - Throws: ``ResponseError`` if an error occurs. - public func querySentJoinRequests(lastUpdatedDate: Date? = nil) -> Promise> { - return turmsClient.driver + public func querySentJoinRequests(lastUpdatedDate: Date? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.queryGroupJoinRequestsRequest = .with { - if let v = lastUpdatedDate { - $0.lastUpdatedDate = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDate = lastUpdatedDate.toMillis() } } - } - .map { - try $0.toResponse { - try $0.kind?.getKindData(GroupJoinRequestsWithVersion.self) - } - } + }).toResponse { + try $0.kind?.getKindData(GroupJoinRequestsWithVersion.self) + } } /// Find group join/membership questions. @@ -1098,22 +1019,19 @@ public class GroupService { groupId: Int64, withAnswers: Bool = false, lastUpdatedDate: Date? = nil - ) -> Promise> { - return turmsClient.driver + ) async throws -> Response { + return try (await turmsClient.driver .send { $0.queryGroupJoinQuestionsRequest = .with { $0.groupID = groupId $0.withAnswers = withAnswers - if let v = lastUpdatedDate { - $0.lastUpdatedDate = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDate = lastUpdatedDate.toMillis() } } - } - .map { - try $0.toResponse { - try $0.kind?.getKindData(GroupJoinQuestionsWithVersion.self) - } - } + }).toResponse { + try $0.kind?.getKindData(GroupJoinQuestionsWithVersion.self) + } } /// Answer group join/membership questions, and join the group automatically @@ -1126,26 +1044,23 @@ public class GroupService { /// - Returns: The group membership questions answer result. /// /// - Throws: ``ResponseError`` if an error occurs. - public func answerGroupQuestions(_ questionIdToAnswer: [Int64: String]) -> Promise> { + public func answerGroupQuestions(_ questionIdToAnswer: [Int64: String]) async throws -> Response { if questionIdToAnswer.isEmpty { - return Promise.value(Response.value(GroupJoinQuestionsAnswerResult())) + return Response.value(GroupJoinQuestionsAnswerResult()) } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.checkGroupJoinQuestionsAnswersRequest = .with { $0.questionIDToAnswer = questionIdToAnswer } + }).toResponse { + let result = try $0.kind?.getKindData(GroupJoinQuestionsAnswerResult.self) + if let result { + return result + } else { + throw ResponseError(code: ResponseStatusCode.invalidResponse) } - .map { - try $0.toResponse { - let result = try $0.kind?.getKindData(GroupJoinQuestionsAnswerResult.self) - if let value = result { - return value - } else { - throw ResponseError(code: ResponseStatusCode.invalidResponse) - } - } - } + } } // Group Member @@ -1196,29 +1111,26 @@ public class GroupService { name: String? = nil, role: GroupMemberRole? = nil, muteEndDate: Date? = nil - ) -> Promise> { + ) async throws -> Response { if userIds.isEmpty { - return Promise.value(Response.empty()) + return Response.empty() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.createGroupMembersRequest = .with { $0.groupID = groupId $0.userIds = userIds - if let v = name { - $0.name = v + if let name { + $0.name = name } - if let v = role { - $0.role = v + if let role { + $0.role = role } - if let v = muteEndDate { - $0.muteEndDate = v.toMillis() + if let muteEndDate { + $0.muteEndDate = muteEndDate.toMillis() } } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Join a group. @@ -1244,11 +1156,11 @@ public class GroupService { /// - name: The name as the group member. /// /// - Throws: ``ResponseError`` if an error occurs. - public func joinGroup(groupId: Int64, name: String? = nil) -> Promise> { + public func joinGroup(groupId: Int64, name: String? = nil) async throws -> Response { guard let info = turmsClient.userService.userInfo else { - return Promise(error: ResponseError(code: .clientSessionHasBeenClosed)) + throw ResponseError(code: .clientSessionHasBeenClosed) } - return addGroupMembers(groupId: groupId, userIds: [info.userId], name: name) + return try await addGroupMembers(groupId: groupId, userIds: [info.userId], name: name) } /// Quit a group. @@ -1276,23 +1188,20 @@ public class GroupService { /// ``ResponseError`` with the code ``ResponseStatusCode/notGroupOwnerToTransferGroup`` will be thrown. /// /// - Throws: ``ResponseError`` if an error occurs. - public func quitGroup(groupId: Int64, successorId: Int64? = nil, quitAfterTransfer: Bool? = nil) -> Promise> { - return turmsClient.driver + public func quitGroup(groupId: Int64, successorId: Int64? = nil, quitAfterTransfer: Bool? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.deleteGroupMembersRequest = .with { $0.groupID = groupId $0.memberIds = [turmsClient.userService.userInfo!.userId] - if let v = successorId { - $0.successorID = v + if let successorId { + $0.successorID = successorId } - if let v = quitAfterTransfer { - $0.quitAfterTransfer = v + if let quitAfterTransfer { + $0.quitAfterTransfer = quitAfterTransfer } } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Remove group members. @@ -1314,20 +1223,17 @@ public class GroupService { /// - memberIds: The target member IDs. /// /// - Throws: ``ResponseError`` if an error occurs. - public func removeGroupMembers(groupId: Int64, memberIds: [Int64]) -> Promise> { + public func removeGroupMembers(groupId: Int64, memberIds: [Int64]) async throws -> Response { if memberIds.isEmpty { - return Promise.value(Response.empty()) + return Response.empty() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.deleteGroupMembersRequest = .with { $0.groupID = groupId $0.memberIds = memberIds } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Remove a group member. @@ -1349,8 +1255,8 @@ public class GroupService { /// - memberId: The target member ID. /// /// - Throws: ``ResponseError`` if an error occurs. - public func removeGroupMember(groupId: Int64, memberId: Int64) -> Promise> { - return removeGroupMembers(groupId: groupId, memberIds: [memberId]) + public func removeGroupMember(groupId: Int64, memberId: Int64) async throws -> Response { + return try await removeGroupMembers(groupId: groupId, memberIds: [memberId]) } /// Update group member info. @@ -1390,29 +1296,26 @@ public class GroupService { name: String? = nil, role: GroupMemberRole? = nil, muteEndDate: Date? = nil - ) -> Promise> { + ) async throws -> Response { if Validator.areAllNil(name, role, muteEndDate) { - return Promise.value(Response.empty()) + return Response.empty() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.updateGroupMemberRequest = .with { $0.groupID = groupId $0.memberID = memberId - if let v = name { - $0.name = v + if let name { + $0.name = name } - if let v = role { - $0.role = v + if let role { + $0.role = role } - if let v = muteEndDate { - $0.muteEndDate = v.toMillis() + if let muteEndDate { + $0.muteEndDate = muteEndDate.toMillis() } } - } - .map { - try $0.toResponse() - } + }).toResponse() } /// Mute group member. @@ -1437,8 +1340,8 @@ public class GroupService { /// - muteEndDate: The new mute end date of the group member. /// /// - Throws: ``ResponseError`` if an error occurs. - public func muteGroupMember(groupId: Int64, memberId: Int64, muteEndDate: Date) -> Promise> { - return updateGroupMemberInfo( + public func muteGroupMember(groupId: Int64, memberId: Int64, muteEndDate: Date) async throws -> Response { + return try await updateGroupMemberInfo( groupId: groupId, memberId: memberId, muteEndDate: muteEndDate @@ -1466,8 +1369,8 @@ public class GroupService { /// - memberId: The target member ID. /// /// - Throws: ``ResponseError`` if an error occurs. - public func unmuteGroupMember(groupId: Int64, memberId: Int64) -> Promise> { - return muteGroupMember( + public func unmuteGroupMember(groupId: Int64, memberId: Int64) async throws -> Response { + return try await muteGroupMember( groupId: groupId, memberId: memberId, muteEndDate: Date(timeIntervalSince1970: 0) @@ -1487,22 +1390,19 @@ public class GroupService { /// Note: The version can be used to update the last updated date stored locally. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryGroupMembers(groupId: Int64, withStatus: Bool = false, lastUpdatedDate: Date? = nil) -> Promise> { - return turmsClient.driver + public func queryGroupMembers(groupId: Int64, withStatus: Bool = false, lastUpdatedDate: Date? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.queryGroupMembersRequest = .with { $0.groupID = groupId - if let v = lastUpdatedDate { - $0.lastUpdatedDate = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDate = lastUpdatedDate.toMillis() } $0.withStatus = withStatus } - } - .map { - try $0.toResponse { - try $0.kind?.getKindData(GroupMembersWithVersion.self) - } - } + }).toResponse { + try $0.kind?.getKindData(GroupMembersWithVersion.self) + } } /// Find group members. @@ -1516,19 +1416,16 @@ public class GroupService { /// Note: The version can be used to update the last updated date stored locally. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryGroupMembersByMemberIds(groupId: Int64, memberIds: [Int64], withStatus: Bool = false) -> Promise> { - return turmsClient.driver + public func queryGroupMembersByMemberIds(groupId: Int64, memberIds: [Int64], withStatus: Bool = false) async throws -> Response { + return try (await turmsClient.driver .send { $0.queryGroupMembersRequest = .with { $0.groupID = groupId $0.memberIds = memberIds $0.withStatus = withStatus } - } - .map { - try $0.toResponse { - try $0.kind?.getKindData(GroupMembersWithVersion.self) - } - } + }).toResponse { + try $0.kind?.getKindData(GroupMembersWithVersion.self) + } } -} \ No newline at end of file +} diff --git a/turms-client-swift/Sources/TurmsClient/Service/MessageService.swift b/turms-client-swift/Sources/TurmsClient/Service/MessageService.swift index eeed0bd74d..6716a010b3 100755 --- a/turms-client-swift/Sources/TurmsClient/Service/MessageService.swift +++ b/turms-client-swift/Sources/TurmsClient/Service/MessageService.swift @@ -1,5 +1,4 @@ import Foundation -import PromiseKit public class MessageService { /** @@ -21,7 +20,7 @@ public class MessageService { return Array(NSOrderedSet(array: userIds)) as! [Int64] } - private weak var turmsClient: TurmsClient! + private unowned var turmsClient: TurmsClient private var mentionedUserIdsParser: ((Message) -> [Int64])? public var messageListeners: [(Message, MessageAddition) -> Void] = [] @@ -115,14 +114,14 @@ public class MessageService { records: [Data]? = nil, burnAfter: Int32? = nil, preMessageId: Int64? = nil - ) -> Promise> { + ) async throws -> Response { if Validator.areAllNil(text, records) { - return Promise(error: ResponseError( + throw ResponseError( code: ResponseStatusCode.illegalArgument, reason: "text and records must not all be null" - )) + ) } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.createMessageRequest = .with { if isGroupMessage { @@ -130,28 +129,25 @@ public class MessageService { } else { $0.recipientID = targetId } - if let v = deliveryDate { - $0.deliveryDate = v.toMillis() + if let deliveryDate { + $0.deliveryDate = deliveryDate.toMillis() } - if let v = text { - $0.text = v + if let text { + $0.text = text } - if let v = records { - $0.records = v + if let records { + $0.records = records } - if let v = burnAfter { - $0.burnAfter = v + if let burnAfter { + $0.burnAfter = burnAfter } - if let v = preMessageId { - $0.preMessageID = v + if let preMessageId { + $0.preMessageID = preMessageId } } - } - .map { - try $0.toResponse { - try $0.getLongOrThrow() - } - } + }).toResponse { + try $0.getLongOrThrow() + } } /// Forward a message. @@ -182,8 +178,8 @@ public class MessageService { messageId: Int64, isGroupMessage: Bool, targetId: Int64 - ) -> Promise> { - return turmsClient.driver + ) async throws -> Response { + return try (await turmsClient.driver .send { $0.createMessageRequest = .with { $0.messageID = messageId @@ -193,12 +189,9 @@ public class MessageService { $0.recipientID = targetId } } - } - .map { - try $0.toResponse { - try $0.getLongOrThrow() - } - } + }).toResponse { + try $0.getLongOrThrow() + } } /// Update a sent message. @@ -233,25 +226,23 @@ public class MessageService { messageId: Int64, text: String? = nil, records: [Data]? = nil - ) -> Promise> { + ) async throws -> Response { if Validator.areAllNil(text, records) { - return Promise.value(Response.empty()) + return Response.empty() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.updateMessageRequest = .with { $0.messageID = messageId - if let v = text { - $0.text = v + if let text { + $0.text = text } - if let v = records { - $0.records = v + if let records { + $0.records = records } } } - .map { - try $0.toResponse() - } + ).toResponse() } /// Find messages. @@ -288,40 +279,37 @@ public class MessageService { deliveryDateEnd: Date? = nil, maxCount: Int32 = 50, descending: Bool? = nil - ) -> Promise> { - return turmsClient.driver + ) async throws -> Response<[Message]> { + return try (await turmsClient.driver .send { $0.queryMessagesRequest = .with { - if let v = ids { - $0.ids = v + if let ids { + $0.ids = ids } - if let v = areGroupMessages { - $0.areGroupMessages = v + if let areGroupMessages { + $0.areGroupMessages = areGroupMessages } - if let v = areSystemMessages { - $0.areSystemMessages = v + if let areSystemMessages { + $0.areSystemMessages = areSystemMessages } - if let v = fromIds { - $0.fromIds = v + if let fromIds { + $0.fromIds = fromIds } - if let v = deliveryDateStart { - $0.deliveryDateStart = v.toMillis() + if let deliveryDateStart { + $0.deliveryDateStart = deliveryDateStart.toMillis() } - if let v = deliveryDateEnd { - $0.deliveryDateEnd = v.toMillis() + if let deliveryDateEnd { + $0.deliveryDateEnd = deliveryDateEnd.toMillis() } $0.maxCount = maxCount - if let v = descending, v { + if let descending, descending { $0.descending = true } $0.withTotal = false } - } - .map { - try $0.toResponse { - $0.messages.messages - } - } + }).toResponse { + $0.messages.messages + } } /// Find the pair of messages and the total count for each conversation. @@ -358,40 +346,37 @@ public class MessageService { deliveryDateEnd: Date? = nil, maxCount: Int32 = 1, descending: Bool? = nil - ) -> Promise> { - return turmsClient.driver + ) async throws -> Response<[MessagesWithTotal]> { + return try (await turmsClient.driver .send { $0.queryMessagesRequest = .with { - if let v = ids { - $0.ids = v + if let ids { + $0.ids = ids } - if let v = areGroupMessages { - $0.areGroupMessages = v + if let areGroupMessages { + $0.areGroupMessages = areGroupMessages } - if let v = areSystemMessages { - $0.areSystemMessages = v + if let areSystemMessages { + $0.areSystemMessages = areSystemMessages } - if let v = fromIds { - $0.fromIds = v + if let fromIds { + $0.fromIds = fromIds } - if let v = deliveryDateStart { - $0.deliveryDateStart = v.toMillis() + if let deliveryDateStart { + $0.deliveryDateStart = deliveryDateStart.toMillis() } - if let v = deliveryDateEnd { - $0.deliveryDateEnd = v.toMillis() + if let deliveryDateEnd { + $0.deliveryDateEnd = deliveryDateEnd.toMillis() } $0.maxCount = maxCount - if let v = descending, v { + if let descending, descending { $0.descending = true } $0.withTotal = true } - } - .map { - try $0.toResponse { - $0.messagesWithTotalList.messagesWithTotalList - } - } + }).toResponse { + $0.messagesWithTotalList.messagesWithTotalList + } } /// Recall a message. @@ -415,17 +400,15 @@ public class MessageService { /// If null, the current date will be used. /// /// - Throws: ``ResponseError`` if an error occurs. - public func recallMessage(messageId: Int64, recallDate: Date = Date()) -> Promise> { - return turmsClient.driver + public func recallMessage(messageId: Int64, recallDate: Date = Date()) async throws -> Response { + return try (await turmsClient.driver .send { $0.updateMessageRequest = .with { $0.messageID = messageId $0.recallDate = recallDate.toMillis() } } - .map { - try $0.toResponse() - } + ).toResponse() } public func isMentionEnabled() -> Bool { @@ -444,8 +427,8 @@ public class MessageService { return try! UserLocation.with { $0.latitude = latitude $0.longitude = longitude - if let v = details { - $0.details = v + if let details { + $0.details = details } } .serializedData() @@ -454,14 +437,14 @@ public class MessageService { public static func generateAudioRecordByDescription(url: String, duration: Int32? = nil, format: String? = nil, size: Int32? = nil) -> Data { return try! AudioFile.with { $0.description_p.url = url - if let v = duration { - $0.description_p.duration = v + if let duration { + $0.description_p.duration = duration } - if let v = format { - $0.description_p.format = v + if let format { + $0.description_p.format = format } - if let v = size { - $0.description_p.size = v + if let size { + $0.description_p.size = size } } .serializedData() @@ -477,14 +460,14 @@ public class MessageService { public static func generateVideoRecordByDescription(url: String, duration: Int32? = nil, format: String? = nil, size: Int32? = nil) -> Data { return try! VideoFile.with { $0.description_p.url = url - if let v = duration { - $0.description_p.duration = v + if let duration { + $0.description_p.duration = duration } - if let v = format { - $0.description_p.format = v + if let format { + $0.description_p.format = format } - if let v = size { - $0.description_p.size = v + if let size { + $0.description_p.size = size } } .serializedData() @@ -507,14 +490,14 @@ public class MessageService { public static func generateImageRecordByDescription(url: String, fileSize: Int32? = nil, imageSize: Int32? = nil, original: Bool? = nil) -> Data { return try! ImageFile.with { $0.description_p.url = url - if let v = fileSize { - $0.description_p.fileSize = v + if let fileSize { + $0.description_p.fileSize = fileSize } - if let v = imageSize { - $0.description_p.imageSize = v + if let imageSize { + $0.description_p.imageSize = imageSize } - if let v = original { - $0.description_p.original = v + if let original { + $0.description_p.original = original } } .serializedData() @@ -530,11 +513,11 @@ public class MessageService { public static func generateFileRecordByDescription(url: String, format: String? = nil, size: Int32? = nil) -> Data { return try! File.with { $0.description_p.url = url - if let v = format { - $0.description_p.format = v + if let format { + $0.description_p.format = format } - if let v = size { - $0.description_p.size = v + if let size { + $0.description_p.size = size } } .serializedData() @@ -575,7 +558,7 @@ public class MessageService { if request.hasText { $0.text = request.text } - if request.records.count > 0 { + if !request.records.isEmpty { $0.records = request.records } $0.senderID = requesterId diff --git a/turms-client-swift/Sources/TurmsClient/Service/NotificationService.swift b/turms-client-swift/Sources/TurmsClient/Service/NotificationService.swift index 63f804c4cb..c38978e49d 100644 --- a/turms-client-swift/Sources/TurmsClient/Service/NotificationService.swift +++ b/turms-client-swift/Sources/TurmsClient/Service/NotificationService.swift @@ -1,7 +1,7 @@ import Foundation public class NotificationService { - private weak var turmsClient: TurmsClient! + private unowned var turmsClient: TurmsClient public var notificationListeners: [(Notification) -> Void] = [] init(_ turmsClient: TurmsClient) { diff --git a/turms-client-swift/Sources/TurmsClient/Service/StorageService.swift b/turms-client-swift/Sources/TurmsClient/Service/StorageService.swift index 9d5c36fbb2..0f9b4a49a3 100644 --- a/turms-client-swift/Sources/TurmsClient/Service/StorageService.swift +++ b/turms-client-swift/Sources/TurmsClient/Service/StorageService.swift @@ -1,5 +1,4 @@ import Foundation -import PromiseKit public class StorageService { private static let RESOURCE_ID_KEY_NAME = "id" @@ -8,7 +7,7 @@ public class StorageService { .allCases.map { ($0, "\($0)".camelCaseToSnakeCase) }) - private weak var turmsClient: TurmsClient! + private unowned var turmsClient: TurmsClient var serverUrl: String init(_ turmsClient: TurmsClient, storageServerUrl: String? = nil) { @@ -18,565 +17,522 @@ public class StorageService { // User profile picture - public func uploadUserProfilePicture(data: Data, name: String? = nil, mediaType: String? = nil, urlKeyName: String? = nil, customAttributes: [Value]? = nil) -> Promise> { + public func uploadUserProfilePicture(data: Data, name: String? = nil, mediaType: String? = nil, urlKeyName: String? = nil, customAttributes: [Value]? = nil) async throws -> Response { if data.isEmpty { - return Promise(error: ResponseError( + throw ResponseError( code: .illegalArgument, reason: "The data of user profile picture must not be empty" - )) - } - return queryUserProfilePictureUploadInfo(name: name, mediaType: mediaType, customAttributes: customAttributes) - .then { uploadInfo -> Promise> in - var infoData = uploadInfo.data - let url = try self.getAndRemoveResourceUrl(&infoData, urlKeyName ?? StorageService.DEFAULT_URL_KEY_NAME) - let id = infoData.removeValue(forKey: StorageService.RESOURCE_ID_KEY_NAME) - guard let id = id else { - throw ResponseError( - code: ResponseStatusCode.dataNotFound, - reason: "Could not get the resource ID because the key \"\(StorageService.RESOURCE_ID_KEY_NAME)\" does not exist in the data: \(uploadInfo.data)" - ) - } - return self.upload(url: url, formData: infoData, data: data, id: id, name: name, mediaType: mediaType) - } + ) + } + let uploadInfo = try await queryUserProfilePictureUploadInfo(name: name, mediaType: mediaType, customAttributes: customAttributes) + + var infoData = uploadInfo.data + let url = try getAndRemoveResourceUrl(&infoData, urlKeyName ?? StorageService.DEFAULT_URL_KEY_NAME) + let id = infoData.removeValue(forKey: StorageService.RESOURCE_ID_KEY_NAME) + guard let id else { + throw ResponseError( + code: ResponseStatusCode.dataNotFound, + reason: "Could not get the resource ID because the key \"\(StorageService.RESOURCE_ID_KEY_NAME)\" does not exist in the data: \(uploadInfo.data)" + ) + } + return try await upload(url: url, formData: infoData, data: data, id: id, name: name, mediaType: mediaType) } - public func deleteUserProfilePicture(customAttributes: [Value]? = nil) -> Promise> { - return deleteResource(type: .userProfilePicture, customAttributes: customAttributes) + public func deleteUserProfilePicture(customAttributes: [Value]? = nil) async throws -> Response { + return try await deleteResource(type: .userProfilePicture, customAttributes: customAttributes) } - public func queryUserProfilePicture(userId: Int64, fetchDownloadInfo: Bool = false, urlKeyName: String? = nil, customAttributes: [Value]? = nil) -> Promise> { - return queryUserProfilePictureDownloadInfo(userId: userId, fetch: fetchDownloadInfo, urlKeyName: urlKeyName, customAttributes: customAttributes) - .then { downloadInfo -> Promise> in - let url = try self.getResourceUrl(downloadInfo.data, urlKeyName ?? StorageService.DEFAULT_URL_KEY_NAME) - return self.getResource(url) - } + public func queryUserProfilePicture(userId: Int64, fetchDownloadInfo: Bool = false, urlKeyName: String? = nil, customAttributes: [Value]? = nil) async throws -> Response { + let downloadInfo = try await queryUserProfilePictureDownloadInfo(userId: userId, fetch: fetchDownloadInfo, urlKeyName: urlKeyName, customAttributes: customAttributes) + let url = try getResourceUrl(downloadInfo.data, urlKeyName ?? StorageService.DEFAULT_URL_KEY_NAME) + return try await getResource(url) } - public func queryUserProfilePictureUploadInfo(name: String? = nil, mediaType: String? = nil, customAttributes: [Value]? = nil) -> Promise> { - return queryResourceUploadInfo(type: .userProfilePicture, name: name, mediaType: mediaType, customAttributes: customAttributes) + public func queryUserProfilePictureUploadInfo(name: String? = nil, mediaType: String? = nil, customAttributes: [Value]? = nil) async throws -> Response<[String: String]> { + return try await queryResourceUploadInfo(type: .userProfilePicture, name: name, mediaType: mediaType, customAttributes: customAttributes) } - public func queryUserProfilePictureDownloadInfo(userId: Int64, fetch: Bool = false, urlKeyName: String? = nil, customAttributes: [Value]? = nil) -> Promise> { + public func queryUserProfilePictureDownloadInfo(userId: Int64, fetch: Bool = false, urlKeyName: String? = nil, customAttributes: [Value]? = nil) async throws -> Response<[String: String]> { if fetch { - return queryResourceDownloadInfo(type: .userProfilePicture, idNum: userId, customAttributes: customAttributes) + return try await queryResourceDownloadInfo(type: .userProfilePicture, idNum: userId, customAttributes: customAttributes) } let url = "\(serverUrl)/\(getBucketName(.userProfilePicture))/\(userId)" - return Promise.value(Response.value([ + return Response.value([ urlKeyName ?? StorageService.DEFAULT_URL_KEY_NAME: url, - ])) + ]) } // Group profile picture - public func uploadGroupProfilePicture(groupId: Int64, data: Data, name: String? = nil, mediaType: String? = nil, urlKeyName: String? = nil, customAttributes _: [Value]? = nil) -> Promise> { + public func uploadGroupProfilePicture(groupId: Int64, data: Data, name: String? = nil, mediaType: String? = nil, urlKeyName: String? = nil, customAttributes _: [Value]? = nil) async throws -> Response { if data.isEmpty { - return Promise(error: ResponseError( + throw ResponseError( code: .illegalArgument, reason: "The data of group profile picture must not be empty" - )) - } - return queryGroupProfilePictureUploadInfo(groupId: groupId) - .then { uploadInfo -> Promise> in - var infoData = uploadInfo.data - let url = try self.getAndRemoveResourceUrl(&infoData, urlKeyName ?? StorageService.DEFAULT_URL_KEY_NAME) - let id = infoData.removeValue(forKey: StorageService.RESOURCE_ID_KEY_NAME) - guard let id = id else { - throw ResponseError( - code: ResponseStatusCode.dataNotFound, - reason: "Could not get the resource ID because the key \"\(StorageService.RESOURCE_ID_KEY_NAME)\" does not exist in the data: \(uploadInfo.data)" - ) - } - return self.upload(url: url, formData: infoData, data: data, id: id, name: name, mediaType: mediaType) - } + ) + } + let uploadInfo = try await queryGroupProfilePictureUploadInfo(groupId: groupId) + var infoData = uploadInfo.data + let url = try getAndRemoveResourceUrl(&infoData, urlKeyName ?? StorageService.DEFAULT_URL_KEY_NAME) + let id = infoData.removeValue(forKey: StorageService.RESOURCE_ID_KEY_NAME) + guard let id else { + throw ResponseError( + code: ResponseStatusCode.dataNotFound, + reason: "Could not get the resource ID because the key \"\(StorageService.RESOURCE_ID_KEY_NAME)\" does not exist in the data: \(uploadInfo.data)" + ) + } + return try await upload(url: url, formData: infoData, data: data, id: id, name: name, mediaType: mediaType) } - public func deleteGroupProfilePicture(groupId: Int64, customAttributes: [Value]? = nil) -> Promise> { - return deleteResource(type: .groupProfilePicture, idNum: groupId, customAttributes: customAttributes) + public func deleteGroupProfilePicture(groupId: Int64, customAttributes: [Value]? = nil) async throws -> Response { + return try await deleteResource(type: .groupProfilePicture, idNum: groupId, customAttributes: customAttributes) } - public func queryGroupProfilePicture(groupId: Int64, fetchDownloadInfo: Bool = false, urlKeyName: String? = nil, customAttributes: [Value]? = nil) -> Promise> { - return queryGroupProfilePictureDownloadInfo(groupId: groupId, fetch: fetchDownloadInfo, urlKeyName: urlKeyName, customAttributes: customAttributes) - .then { downloadInfo -> Promise> in - let url = try self.getResourceUrl(downloadInfo.data, urlKeyName ?? StorageService.DEFAULT_URL_KEY_NAME) - return self.getResource(url) - } + public func queryGroupProfilePicture(groupId: Int64, fetchDownloadInfo: Bool = false, urlKeyName: String? = nil, customAttributes: [Value]? = nil) async throws -> Response { + let downloadInfo = try await queryGroupProfilePictureDownloadInfo(groupId: groupId, fetch: fetchDownloadInfo, urlKeyName: urlKeyName, customAttributes: customAttributes) + + let url = try getResourceUrl(downloadInfo.data, urlKeyName ?? StorageService.DEFAULT_URL_KEY_NAME) + return try await getResource(url) } - public func queryGroupProfilePictureUploadInfo(groupId: Int64, name: String? = nil, mediaType: String? = nil, customAttributes: [Value]? = nil) -> Promise> { - return queryResourceUploadInfo(type: .groupProfilePicture, idNum: groupId, name: name, mediaType: mediaType, customAttributes: customAttributes) + public func queryGroupProfilePictureUploadInfo(groupId: Int64, name: String? = nil, mediaType: String? = nil, customAttributes: [Value]? = nil) async throws -> Response<[String: String]> { + return try await queryResourceUploadInfo(type: .groupProfilePicture, idNum: groupId, name: name, mediaType: mediaType, customAttributes: customAttributes) } - public func queryGroupProfilePictureDownloadInfo(groupId: Int64, fetch: Bool = false, urlKeyName: String? = nil, customAttributes: [Value]? = nil) -> Promise> { + public func queryGroupProfilePictureDownloadInfo(groupId: Int64, fetch: Bool = false, urlKeyName: String? = nil, customAttributes: [Value]? = nil) async throws -> Response<[String: String]> { if fetch { - return queryResourceDownloadInfo(type: .groupProfilePicture, idNum: groupId, customAttributes: customAttributes) + return try await queryResourceDownloadInfo(type: .groupProfilePicture, idNum: groupId, customAttributes: customAttributes) } let url = "\(serverUrl)/\(getBucketName(.groupProfilePicture))/\(groupId)" - return Promise.value(Response.value([ + return Response.value([ urlKeyName ?? StorageService.DEFAULT_URL_KEY_NAME: url, - ])) + ]) } // Message attachment - public func uploadMessageAttachment(data: Data, name: String? = nil, mediaType: String? = nil, urlKeyName: String? = nil, customAttributes: [Value]? = nil) -> Promise> { - return uploadMessageAttachment0(data: data, - name: name, - mediaType: mediaType, - urlKeyName: urlKeyName, - customAttributes: customAttributes) + public func uploadMessageAttachment(data: Data, name: String? = nil, mediaType: String? = nil, urlKeyName: String? = nil, customAttributes: [Value]? = nil) async throws -> Response { + return try await uploadMessageAttachment0(data: data, + name: name, + mediaType: mediaType, + urlKeyName: urlKeyName, + customAttributes: customAttributes) } - public func uploadMessageAttachmentInPrivateConversation(userId: Int64, data: Data, name: String? = nil, mediaType: String? = nil, urlKeyName: String? = nil, customAttributes: [Value]? = nil) -> Promise> { - return uploadMessageAttachment0(data: data, - userId: userId, - name: name, - mediaType: mediaType, - urlKeyName: urlKeyName, - customAttributes: customAttributes) + public func uploadMessageAttachmentInPrivateConversation(userId: Int64, data: Data, name: String? = nil, mediaType: String? = nil, urlKeyName: String? = nil, customAttributes: [Value]? = nil) async throws -> Response { + return try await uploadMessageAttachment0(data: data, + userId: userId, + name: name, + mediaType: mediaType, + urlKeyName: urlKeyName, + customAttributes: customAttributes) } - public func uploadMessageAttachmentInGroupConversation(groupId: Int64, data: Data, name: String? = nil, mediaType: String? = nil, urlKeyName: String? = nil, customAttributes: [Value]? = nil) -> Promise> { - return uploadMessageAttachment0(data: data, - groupId: groupId, - name: name, - mediaType: mediaType, - urlKeyName: urlKeyName, - customAttributes: customAttributes) + public func uploadMessageAttachmentInGroupConversation(groupId: Int64, data: Data, name: String? = nil, mediaType: String? = nil, urlKeyName: String? = nil, customAttributes: [Value]? = nil) async throws -> Response { + return try await uploadMessageAttachment0(data: data, + groupId: groupId, + name: name, + mediaType: mediaType, + urlKeyName: urlKeyName, + customAttributes: customAttributes) } - private func uploadMessageAttachment0(data: Data, userId: Int64? = nil, groupId: Int64? = nil, name: String? = nil, mediaType: String? = nil, urlKeyName: String? = nil, customAttributes: [Value]? = nil) -> Promise> { + private func uploadMessageAttachment0(data: Data, userId: Int64? = nil, groupId: Int64? = nil, name: String? = nil, mediaType: String? = nil, urlKeyName: String? = nil, customAttributes: [Value]? = nil) async throws -> Response { if data.isEmpty { - return Promise(error: ResponseError( + throw ResponseError( code: .illegalArgument, reason: "The data of message attachment must not be empty" - )) + ) } - let queryUploadInfo: Promise> - if userId == nil, groupId == nil { - queryUploadInfo = queryMessageAttachmentUploadInfo( + let uploadInfo = if userId == nil, groupId == nil { + try await queryMessageAttachmentUploadInfo( name: name, mediaType: mediaType, customAttributes: customAttributes ) } else if userId != nil { if groupId != nil { - return Promise(error: ResponseError( + throw ResponseError( code: .illegalArgument, reason: "The user ID and the group ID must not both be non-null" - )) + ) + } else { + try await queryMessageAttachmentUploadInfoInPrivateConversation( + userId: userId!, + name: name, + mediaType: mediaType, + customAttributes: customAttributes + ) } - queryUploadInfo = queryMessageAttachmentUploadInfoInPrivateConversation( - userId: userId!, - name: name, - mediaType: mediaType, - customAttributes: customAttributes - ) } else { - queryUploadInfo = queryMessageAttachmentUploadInfoInGroupConversation( + try await queryMessageAttachmentUploadInfoInGroupConversation( groupId: groupId!, name: name, mediaType: mediaType, customAttributes: customAttributes ) } - return queryUploadInfo - .then { uploadInfo -> Promise> in - var infoData = uploadInfo.data - let url = try self.getAndRemoveResourceUrl(&infoData, urlKeyName ?? StorageService.DEFAULT_URL_KEY_NAME) - let id = infoData.removeValue(forKey: StorageService.RESOURCE_ID_KEY_NAME) - guard let id = id else { - throw ResponseError( - code: .dataNotFound, - reason: "Could not get the resource ID because the key \"\(StorageService.RESOURCE_ID_KEY_NAME)\" does not exist in the data: \(uploadInfo.data)" - ) - } - return self.upload(url: url, formData: infoData, data: data, id: id, name: name, mediaType: mediaType) - } + + var infoData = uploadInfo.data + let url = try getAndRemoveResourceUrl(&infoData, urlKeyName ?? StorageService.DEFAULT_URL_KEY_NAME) + let id = infoData.removeValue(forKey: StorageService.RESOURCE_ID_KEY_NAME) + guard let id else { + throw ResponseError( + code: .dataNotFound, + reason: "Could not get the resource ID because the key \"\(StorageService.RESOURCE_ID_KEY_NAME)\" does not exist in the data: \(uploadInfo.data)" + ) + } + return try await upload(url: url, formData: infoData, data: data, id: id, name: name, mediaType: mediaType) } - public func deleteMessageAttachment(attachmentIdNum: Int64? = nil, attachmentIdStr: String? = nil, customAttributes: [Value]? = nil) -> Promise> { + public func deleteMessageAttachment(attachmentIdNum: Int64? = nil, attachmentIdStr: String? = nil, customAttributes: [Value]? = nil) async throws -> Response { if Validator.areAllNullOrNonNull(attachmentIdNum, attachmentIdStr) { - return Promise(error: ResponseError( + throw ResponseError( code: ResponseStatusCode.illegalArgument, reason: "One and only one attachment ID must be specified" - )) + ) } - return deleteResource(type: .messageAttachment, idNum: attachmentIdNum, idStr: attachmentIdStr, customAttributes: customAttributes) + return try await deleteResource(type: .messageAttachment, idNum: attachmentIdNum, idStr: attachmentIdStr, customAttributes: customAttributes) } - public func shareMessageAttachmentWithUser(userId: Int64, attachmentIdNum: Int64? = nil, attachmentIdStr: String? = nil) -> Promise> { + public func shareMessageAttachmentWithUser(userId: Int64, attachmentIdNum: Int64? = nil, attachmentIdStr: String? = nil) async throws -> Response { if Validator.areAllNullOrNonNull(attachmentIdNum, attachmentIdStr) { - return Promise(error: ResponseError( + throw ResponseError( code: ResponseStatusCode.illegalArgument, reason: "One and only one attachment ID must be specified" - )) + ) } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.updateMessageAttachmentInfoRequest = .with { $0.userIDToShareWith = userId - if let v = attachmentIdNum { - $0.attachmentIDNum = v + if let attachmentIdNum { + $0.attachmentIDNum = attachmentIdNum } - if let v = attachmentIdStr { - $0.attachmentIDStr = v + if let attachmentIdStr { + $0.attachmentIDStr = attachmentIdStr } } } - .map { - try $0.toResponse() - } + ).toResponse() } - public func shareMessageAttachmentWithGroup(groupId: Int64, attachmentIdNum: Int64? = nil, attachmentIdStr: String? = nil) -> Promise> { + public func shareMessageAttachmentWithGroup(groupId: Int64, attachmentIdNum: Int64? = nil, attachmentIdStr: String? = nil) async throws -> Response { if Validator.areAllNullOrNonNull(attachmentIdNum, attachmentIdStr) { - return Promise(error: ResponseError( + throw ResponseError( code: ResponseStatusCode.illegalArgument, reason: "One and only one attachment ID must be specified" - )) + ) } - return turmsClient.driver.send { + return try (await turmsClient.driver.send { $0.updateMessageAttachmentInfoRequest = .with { $0.groupIDToShareWith = groupId - if let v = attachmentIdNum { - $0.attachmentIDNum = v + if let attachmentIdNum { + $0.attachmentIDNum = attachmentIdNum } - if let v = attachmentIdStr { - $0.attachmentIDStr = v + if let attachmentIdStr { + $0.attachmentIDStr = attachmentIdStr } } } - .map { - try $0.toResponse() - } + ).toResponse() } - public func unshareMessageAttachmentWithUser(userId: Int64, attachmentIdNum: Int64? = nil, attachmentIdStr: String? = nil) -> Promise> { + public func unshareMessageAttachmentWithUser(userId: Int64, attachmentIdNum: Int64? = nil, attachmentIdStr: String? = nil) async throws -> Response { if Validator.areAllNullOrNonNull(attachmentIdNum, attachmentIdStr) { - return Promise(error: ResponseError( + throw ResponseError( code: ResponseStatusCode.illegalArgument, reason: "One and only one attachment ID must be specified" - )) + ) } - return turmsClient.driver.send { + return try (await turmsClient.driver.send { $0.updateMessageAttachmentInfoRequest = .with { $0.userIDToUnshareWith = userId - if let v = attachmentIdNum { - $0.attachmentIDNum = v + if let attachmentIdNum { + $0.attachmentIDNum = attachmentIdNum } - if let v = attachmentIdStr { - $0.attachmentIDStr = v + if let attachmentIdStr { + $0.attachmentIDStr = attachmentIdStr } } } - .map { - try $0.toResponse() - } + ).toResponse() } - public func unshareMessageAttachmentWithGroup(groupId: Int64, attachmentIdNum: Int64? = nil, attachmentIdStr: String? = nil) -> Promise> { + public func unshareMessageAttachmentWithGroup(groupId: Int64, attachmentIdNum: Int64? = nil, attachmentIdStr: String? = nil) async throws -> Response { if Validator.areAllNullOrNonNull(attachmentIdNum, attachmentIdStr) { - return Promise(error: ResponseError( + throw ResponseError( code: ResponseStatusCode.illegalArgument, reason: "One and only one attachment ID must be specified" - )) + ) } - return turmsClient.driver.send { + return try (await turmsClient.driver.send { $0.updateMessageAttachmentInfoRequest = .with { $0.groupIDToUnshareWith = groupId - if let v = attachmentIdNum { - $0.attachmentIDNum = v + if let attachmentIdNum { + $0.attachmentIDNum = attachmentIdNum } - if let v = attachmentIdStr { - $0.attachmentIDStr = v + if let attachmentIdStr { + $0.attachmentIDStr = attachmentIdStr } } } - .map { - try $0.toResponse() - } + ).toResponse() } - public func queryMessageAttachment(attachmentIdNum: Int64? = nil, attachmentIdStr: String? = nil, fetchDownloadInfo: Bool = false, urlKeyName: String? = nil, customAttributes: [Value]? = nil) -> Promise> { - return queryMessageAttachmentDownloadInfo(attachmentIdNum: attachmentIdNum, - attachmentIdStr: attachmentIdStr, - fetch: fetchDownloadInfo, - urlKeyName: urlKeyName, - customAttributes: customAttributes) - .then { downloadInfo -> Promise> in - let url = try self.getResourceUrl(downloadInfo.data, urlKeyName ?? StorageService.DEFAULT_URL_KEY_NAME) - return self.getResource(url) - } + public func queryMessageAttachment(attachmentIdNum: Int64? = nil, attachmentIdStr: String? = nil, fetchDownloadInfo: Bool = false, urlKeyName: String? = nil, customAttributes: [Value]? = nil) async throws -> Response { + let downloadInfo = try await queryMessageAttachmentDownloadInfo(attachmentIdNum: attachmentIdNum, + attachmentIdStr: attachmentIdStr, + fetch: fetchDownloadInfo, + urlKeyName: urlKeyName, + customAttributes: customAttributes) + + let url = try getResourceUrl(downloadInfo.data, urlKeyName ?? StorageService.DEFAULT_URL_KEY_NAME) + return try await getResource(url) } - public func queryMessageAttachmentUploadInfo(name: String? = nil, mediaType: String? = nil, customAttributes: [Value]? = nil) -> Promise> { - return queryResourceUploadInfo(type: .messageAttachment, name: name, mediaType: mediaType, customAttributes: customAttributes) + public func queryMessageAttachmentUploadInfo(name: String? = nil, mediaType: String? = nil, customAttributes: [Value]? = nil) async throws -> Response<[String: String]> { + return try await queryResourceUploadInfo(type: .messageAttachment, name: name, mediaType: mediaType, customAttributes: customAttributes) } - public func queryMessageAttachmentUploadInfoInPrivateConversation(userId: Int64, name: String? = nil, mediaType: String? = nil, customAttributes: [Value]? = nil) -> Promise> { - return queryResourceUploadInfo(type: .messageAttachment, idNum: userId, name: name, mediaType: mediaType, customAttributes: customAttributes) + public func queryMessageAttachmentUploadInfoInPrivateConversation(userId: Int64, name: String? = nil, mediaType: String? = nil, customAttributes: [Value]? = nil) async throws -> Response<[String: String]> { + return try await queryResourceUploadInfo(type: .messageAttachment, idNum: userId, name: name, mediaType: mediaType, customAttributes: customAttributes) } - public func queryMessageAttachmentUploadInfoInGroupConversation(groupId: Int64, name: String? = nil, mediaType: String? = nil, customAttributes: [Value]? = nil) -> Promise> { - return queryResourceUploadInfo(type: .messageAttachment, idNum: -groupId, name: name, mediaType: mediaType, customAttributes: customAttributes) + public func queryMessageAttachmentUploadInfoInGroupConversation(groupId: Int64, name: String? = nil, mediaType: String? = nil, customAttributes: [Value]? = nil) async throws -> Response<[String: String]> { + return try await queryResourceUploadInfo(type: .messageAttachment, idNum: -groupId, name: name, mediaType: mediaType, customAttributes: customAttributes) } - public func queryMessageAttachmentDownloadInfo(attachmentIdNum: Int64? = nil, attachmentIdStr: String? = nil, fetch: Bool = false, urlKeyName: String? = nil, customAttributes: [Value]? = nil) -> Promise> { + public func queryMessageAttachmentDownloadInfo(attachmentIdNum: Int64? = nil, attachmentIdStr: String? = nil, fetch: Bool = false, urlKeyName: String? = nil, customAttributes: [Value]? = nil) async throws -> Response<[String: String]> { if Validator.areAllNullOrNonNull(attachmentIdNum, attachmentIdStr) { - return Promise(error: ResponseError( + throw ResponseError( code: ResponseStatusCode.illegalArgument, reason: "One and only one attachment ID must be specified" - )) + ) } if fetch { - return queryResourceDownloadInfo(type: .messageAttachment, idNum: attachmentIdNum, idStr: attachmentIdStr, customAttributes: customAttributes) + return try await queryResourceDownloadInfo(type: .messageAttachment, idNum: attachmentIdNum, idStr: attachmentIdStr, customAttributes: customAttributes) } let url = "\(serverUrl)/\(getBucketName(.messageAttachment))/\(attachmentIdNum?.description ?? attachmentIdStr!)" - return Promise.value(Response.value([ + return Response.value([ urlKeyName ?? StorageService.DEFAULT_URL_KEY_NAME: url, - ])) + ]) } - public func queryMessageAttachmentInfosUploadedByMe(creationDateStart: Date? = nil, creationDateEnd: Date? = nil) -> Promise> { - return turmsClient.driver.send { + public func queryMessageAttachmentInfosUploadedByMe(creationDateStart: Date? = nil, creationDateEnd: Date? = nil) async throws -> Response<[StorageResourceInfo]> { + return try (await turmsClient.driver.send { $0.queryMessageAttachmentInfosRequest = .with { - if let v = creationDateStart { - $0.creationDateStart = v.toMillis() + if let creationDateStart { + $0.creationDateStart = creationDateStart.toMillis() } - if let v = creationDateEnd { - $0.creationDateEnd = v.toMillis() + if let creationDateEnd { + $0.creationDateEnd = creationDateEnd.toMillis() } } - } - .map { - try $0.toResponse { - $0.storageResourceInfos.infos - } + }).toResponse { + $0.storageResourceInfos.infos } } public func queryMessageAttachmentInfosInPrivateConversations(userIds: Set, - areSharedByMe: Bool? = nil, creationDateStart: Date? = nil, creationDateEnd: Date? = nil) -> Promise> + areSharedByMe: Bool? = nil, creationDateStart: Date? = nil, creationDateEnd: Date? = nil) async throws -> Response<[StorageResourceInfo]> { - return turmsClient.driver.send { + return try (await turmsClient.driver.send { $0.queryMessageAttachmentInfosRequest = .with { $0.userIds = Array(userIds) - if let v = areSharedByMe { - $0.areSharedByMe = v + if let areSharedByMe { + $0.areSharedByMe = areSharedByMe } - if let v = creationDateStart { - $0.creationDateStart = v.toMillis() + if let creationDateStart { + $0.creationDateStart = creationDateStart.toMillis() } - if let v = creationDateEnd { - $0.creationDateEnd = v.toMillis() + if let creationDateEnd { + $0.creationDateEnd = creationDateEnd.toMillis() } } - } - .map { - try $0.toResponse { - $0.storageResourceInfos.infos - } + }).toResponse { + $0.storageResourceInfos.infos } } public func queryMessageAttachmentInfosInGroupConversations(groupIds: Set, - userIds: Set? = nil, creationDateStart: Date? = nil, creationDateEnd: Date? = nil) -> Promise> + userIds: Set? = nil, creationDateStart: Date? = nil, creationDateEnd: Date? = nil) async throws -> Response<[StorageResourceInfo]> { - return turmsClient.driver.send { + return try (await turmsClient.driver.send { $0.queryMessageAttachmentInfosRequest = .with { $0.groupIds = Array(groupIds) - if let v = userIds { - $0.userIds = Array(v) + if let userIds { + $0.userIds = Array(userIds) } - if let v = creationDateStart { - $0.creationDateStart = v.toMillis() + if let creationDateStart { + $0.creationDateStart = creationDateStart.toMillis() } - if let v = creationDateEnd { - $0.creationDateEnd = v.toMillis() + if let creationDateEnd { + $0.creationDateEnd = creationDateEnd.toMillis() } } - } - .map { - try $0.toResponse { - $0.storageResourceInfos.infos - } + }).toResponse { + $0.storageResourceInfos.infos } } // Base - private func upload(url: String, formData: [String: String], data: Data, id: String, name: String? = nil, mediaType: String? = nil) -> Promise> { - return Promise { seal in - if data.isEmpty { - seal.reject(ResponseError(code: .illegalArgument, reason: "The data of resource must not be empty")) - return - } - let httpUrl = URL(string: url) - guard let httpUrl = httpUrl else { - seal.reject(ResponseError(code: .illegalArgument, reason: "The URL is illegal: \(url)")) - return - } - let request = MultipartFormDataRequest(url: httpUrl) - for (key, value) in formData { - request.addTextField(named: key, value: value) - } - request.addTextField(named: "key", value: id) - if let mediaType { - request.addTextField(named: "Content-Type", value: mediaType) + private func upload(url: String, formData: [String: String], data: Data, id: String, name: String? = nil, mediaType: String? = nil) async throws -> Response { + if data.isEmpty { + throw ResponseError(code: .illegalArgument, reason: "The data of resource must not be empty") + } + let httpUrl = URL(string: url) + guard let httpUrl else { + throw ResponseError(code: .illegalArgument, reason: "The URL is illegal: \(url)") + } + let request = MultipartFormDataRequest(url: httpUrl) + for (key, value) in formData { + request.addTextField(named: key, value: value) + } + request.addTextField(named: "key", value: id) + if let mediaType { + request.addTextField(named: "Content-Type", value: mediaType) + } + request.addDataField(fieldName: "file", fileName: name ?? id, data: data, mediaType: mediaType) + + var responseData: Data + var response: URLResponse + do { + (responseData, response) = try await URLSession.shared.data(for: request.asURLRequest()) + } catch { + throw ResponseError( + code: .httpError, + reason: "Caught an error while sending an HTTP POST request to update the resource", + cause: error + ) + } + if let response = response as? HTTPURLResponse { + if response.isSuccessful { + let text = String(decoding: responseData, as: UTF8.self) + let headers = Dictionary(uniqueKeysWithValues: response.allHeaderFields.map { + (String(describing: $0.key), String(describing: $0.value)) + }) + let idNum = Int64(id) + let idStr = idNum == nil ? id : nil + return Response.value(StorageUploadResult(uri: httpUrl, metadata: headers, data: text, resourceIdNum: idNum, resourceIdStr: idStr)) + } else { + throw ResponseError( + code: .httpNotSuccessfulResponse, + reason: "Failed to upload the resource because the HTTP response status code is: \(response.statusCode)" + ) } - request.addDataField(fieldName: "file", fileName: name ?? id, data: data, mediaType: mediaType) - - URLSession.shared.dataTask(with: request, completionHandler: { responseData, response, error in - if let error = error { - seal.reject(ResponseError( - code: .httpError, - reason: "Caught an error while sending an HTTP POST request to update the resource", - cause: error - )) - } else if let response = response as? HTTPURLResponse { - if response.isSuccessful { - let text = responseData == nil ? "" : String(decoding: responseData!, as: UTF8.self) - let headers = Dictionary(uniqueKeysWithValues: response.allHeaderFields.map { - (String(describing: $0.key), String(describing: $0.value)) - }) - let idNum = Int64(id) - let idStr = idNum == nil ? id : nil - seal.fulfill(Response.value(StorageUploadResult(uri: httpUrl, metadata: headers, data: text, resourceIdNum: idNum, resourceIdStr: idStr))) - } else { - seal.reject(ResponseError( - code: .httpNotSuccessfulResponse, - reason: "Failed to upload the resource because the HTTP response status code is: \(response.statusCode)" - )) - } - } else { - seal.reject(ResponseError( - code: .invalidResponse, - reason: "Expected the response to be a HTTP response, but got \(response?.className ?? "nil")" - )) - } - }) - .resume() + } else { + throw ResponseError( + code: .invalidResponse, + reason: "Expected the response to be a HTTP response, but got \(response.className)" + ) } } - private func deleteResource(type: StorageResourceType, idNum: Int64? = nil, idStr: String? = nil, customAttributes: [Value]? = nil) -> Promise> { - return turmsClient.driver + private func deleteResource(type: StorageResourceType, idNum: Int64? = nil, idStr: String? = nil, customAttributes: [Value]? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.deleteResourceRequest = .with { $0.type = type - if let v = idNum { - $0.idNum = v + if let idNum { + $0.idNum = idNum } - if let v = idStr { - $0.idStr = v + if let idStr { + $0.idStr = idStr } - if let v = customAttributes { - $0.customAttributes = v + if let customAttributes { + $0.customAttributes = customAttributes } } } - .map { - try $0.toResponse() - } + ).toResponse() } - private func getResource(_ url: String) -> Promise> { - return Promise { seal in - let reqUrl = URL(string: url) - guard let reqUrl = reqUrl else { - seal.reject(ResponseError( - code: .illegalArgument, - reason: "The URL is illegal: \(url)" - )) - return - } - var request = URLRequest(url: reqUrl) - request.httpMethod = "GET" - URLSession.shared.dataTask(with: request) { responseData, response, error in - if let error = error { - seal.reject(ResponseError( - code: .httpError, - reason: "Caught an error while sending an HTTP GET request to retrieve the resource", - cause: error - )) - } else if let response = response as? HTTPURLResponse { - if response.isSuccessful { - let headers = Dictionary(uniqueKeysWithValues: response.allHeaderFields.map { - (String(describing: $0.key), String(describing: $0.value)) - }) - if let responseData = responseData { - seal.fulfill(Response.value(StorageResource(uri: reqUrl, metadata: headers, data: responseData))) - } else { - seal.reject(ResponseError( - code: .invalidResponse, - reason: "Failed to retrieve the resource because the HTTP response body is empty" - )) - } - } else { - seal.reject(ResponseError( - code: .httpNotSuccessfulResponse, - reason: "Failed to retrieve the resource because the HTTP response status code is: \(response.statusCode)" - )) - } - } else { - seal.reject(ResponseError( - code: .invalidResponse, - reason: "Expected the response to be a HTTP response, but got \(response?.className ?? "nil")" - )) - } + private func getResource(_ url: String) async throws -> Response { + let reqUrl = URL(string: url) + guard let reqUrl else { + throw ResponseError( + code: .illegalArgument, + reason: "The URL is illegal: \(url)" + ) + } + var request = URLRequest(url: reqUrl) + request.httpMethod = "GET" + + var responseData: Data + var response: URLResponse + do { + (responseData, response) = try await URLSession.shared.data(for: request) + } catch { + throw ResponseError( + code: .httpError, + reason: "Caught an error while sending an HTTP GET request to retrieve the resource", + cause: error + ) + } + if let response = response as? HTTPURLResponse { + if response.isSuccessful { + let headers = Dictionary(uniqueKeysWithValues: response.allHeaderFields.map { + (String(describing: $0.key), String(describing: $0.value)) + }) + return Response.value(StorageResource(uri: reqUrl, metadata: headers, data: responseData)) + } else { + throw ResponseError( + code: .httpNotSuccessfulResponse, + reason: "Failed to retrieve the resource because the HTTP response status code is: \(response.statusCode)" + ) } - .resume() + } else { + throw ResponseError( + code: .invalidResponse, + reason: "Expected the response to be a HTTP response, but got \(response.className)" + ) } } - private func queryResourceUploadInfo(type: StorageResourceType, idNum: Int64? = nil, idStr: String? = nil, name: String? = nil, mediaType: String? = nil, customAttributes: [Value]? = nil) -> Promise> { - return turmsClient.driver + private func queryResourceUploadInfo(type: StorageResourceType, idNum: Int64? = nil, idStr: String? = nil, name: String? = nil, mediaType: String? = nil, customAttributes: [Value]? = nil) async throws -> Response<[String: String]> { + return try (await turmsClient.driver .send { $0.queryResourceUploadInfoRequest = .with { $0.type = type - if let v = idNum { - $0.idNum = v + if let idNum { + $0.idNum = idNum } - if let v = idStr { - $0.idStr = v + if let idStr { + $0.idStr = idStr } - if let v = name { - $0.name = v + if let name { + $0.name = name } - if let v = mediaType { - $0.mediaType = v + if let mediaType { + $0.mediaType = mediaType } - if let v = customAttributes { - $0.customAttributes = v + if let customAttributes { + $0.customAttributes = customAttributes } } - } - .map { - try $0.toResponse { - try $0.stringsWithVersion.strings.toMap() - } - } + }).toResponse { + try $0.stringsWithVersion.strings.toMap() + } } - private func queryResourceDownloadInfo(type: StorageResourceType, idNum: Int64? = nil, idStr: String? = nil, customAttributes: [Value]? = nil) -> Promise> { - return turmsClient.driver + private func queryResourceDownloadInfo(type: StorageResourceType, idNum: Int64? = nil, idStr: String? = nil, customAttributes: [Value]? = nil) async throws -> Response<[String: String]> { + return try (await turmsClient.driver .send { $0.queryResourceDownloadInfoRequest = .with { $0.type = type - if let v = idNum { - $0.idNum = v + if let idNum { + $0.idNum = idNum } - if let v = idStr { - $0.idStr = v + if let idStr { + $0.idStr = idStr } - if let v = customAttributes { - $0.customAttributes = v + if let customAttributes { + $0.customAttributes = customAttributes } } - } - .map { - try $0.toResponse { - try $0.stringsWithVersion.strings.toMap() - } - } + }).toResponse { + try $0.stringsWithVersion.strings.toMap() + } } private func getBucketName(_ resourceType: StorageResourceType) -> String { @@ -585,7 +541,7 @@ public class StorageService { private func getResourceUrl(_ data: [String: String], _ urlKeyName: String) throws -> String { let url = data[urlKeyName] - if let url = url { + if let url { return url } throw ResponseError( @@ -596,7 +552,7 @@ public class StorageService { private func getAndRemoveResourceUrl(_ data: inout [String: String], _ urlKeyName: String) throws -> String { let url = data.removeValue(forKey: urlKeyName) - if let url = url { + if let url { return url } throw ResponseError( diff --git a/turms-client-swift/Sources/TurmsClient/Service/UserService.swift b/turms-client-swift/Sources/TurmsClient/Service/UserService.swift index 766f861dae..b008211563 100755 --- a/turms-client-swift/Sources/TurmsClient/Service/UserService.swift +++ b/turms-client-swift/Sources/TurmsClient/Service/UserService.swift @@ -1,8 +1,7 @@ import Foundation -import PromiseKit public class UserService { - private weak var turmsClient: TurmsClient! + private unowned var turmsClient: TurmsClient public var userInfo: User? private var storePassword = false @@ -84,7 +83,7 @@ public class UserService { location: Location? = nil, storePassword: Bool = false, certificatePinning: CertificatePinning? = nil - ) -> Promise> { + ) async throws -> Response { var user = User(userId: userId) if storePassword { user.password = password @@ -92,43 +91,39 @@ public class UserService { user.onlineStatus = onlineStatus ?? .available user.deviceType = deviceType ?? .ios user.location = location - let connect: Promise = turmsClient.driver.isConnected - ? Promise.value(()) - : turmsClient.driver.connect(certificatePinning: certificatePinning) - return connect.then { - self.turmsClient.driver.send { - $0.createSessionRequest = .with { - $0.version = 1 - $0.userID = userId - if let v = password { - $0.password = v - } - if let v = deviceType { - $0.deviceType = v - } - if let v = deviceDetails { - $0.deviceDetails = v - } - if let v = onlineStatus { - $0.userStatus = v - } - if let v = location { - $0.location = .with { - $0.longitude = v.longitude - $0.latitude = v.latitude - } + if !turmsClient.driver.isConnected { + try await turmsClient.driver.connect(certificatePinning: certificatePinning) + } + let createSessionResponse = try await turmsClient.driver.send { + $0.createSessionRequest = .with { + $0.version = 1 + $0.userID = userId + if let password { + $0.password = password + } + if let deviceType { + $0.deviceType = deviceType + } + if let deviceDetails { + $0.deviceDetails = deviceDetails + } + if let onlineStatus { + $0.userStatus = onlineStatus + } + if let location { + $0.location = .with { + $0.longitude = location.longitude + $0.latitude = location.latitude } } } - .map { - try $0.toResponse() - } - .get { _ in - self.changeToOnline() - self.userInfo = user - self.storePassword = storePassword - } } + defer { + changeToOnline() + self.userInfo = user + self.storePassword = storePassword + } + return try createSessionResponse.toResponse() } /// Log out. @@ -141,19 +136,18 @@ public class UserService { /// rather than sending a delete session request first and then closing the connection. /// /// - Throws: ``ResponseError`` if an error occurs. - public func logout(disconnect: Bool = true) -> Promise> { - let d: Promise> = disconnect - ? turmsClient.driver.disconnect().map { - Response.empty() - } - : turmsClient.driver.send { + public func logout(disconnect: Bool = true) async throws -> Response { + defer { + changeToOffline(SessionCloseInfo(closeStatus: Int32(SessionCloseStatus.disconnectedByClient.rawValue), businessStatus: nil, reason: nil, cause: nil)) + } + if disconnect { + try await turmsClient.driver.disconnect() + + return Response.empty() + } else { + return try (await turmsClient.driver.send { $0.deleteSessionRequest = DeleteSessionRequest() - } - .map { - try $0.toResponse() - } - return d.get { _ in - self.changeToOffline(SessionCloseInfo(closeStatus: Int32(SessionCloseStatus.disconnectedByClient.rawValue), businessStatus: nil, reason: nil, cause: nil)) + }).toResponse() } } @@ -171,22 +165,20 @@ public class UserService { /// - onlineStatus: The new online status. /// /// - Throws: ``ResponseError`` if an error occurs. - public func updateUserOnlineStatus(_ onlineStatus: UserStatus) -> Promise> { + public func updateUserOnlineStatus(_ onlineStatus: UserStatus) async throws -> Response { if onlineStatus == .offline { - return Promise(error: ResponseError( + throw ResponseError( code: ResponseStatusCode.illegalArgument, reason: "The online status must not be OFFLINE" - )) + ) } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.updateUserOnlineStatusRequest = .with { $0.userStatus = onlineStatus } } - .map { - try $0.toResponse() - } + ).toResponse() } /// Disconnect the online devices of the logged-in user. @@ -198,17 +190,15 @@ public class UserService { /// - deviceTypes: The device types to disconnect. /// /// - Throws: ``ResponseError`` if an error occurs. - public func disconnectOnlineDevices(_ deviceTypes: [DeviceType]) -> Promise> { - return turmsClient.driver + public func disconnectOnlineDevices(_ deviceTypes: [DeviceType]) async throws -> Response { + return try (await turmsClient.driver .send { $0.updateUserOnlineStatusRequest = .with { $0.userStatus = UserStatus.offline $0.deviceTypes = deviceTypes } } - .map { - try $0.toResponse() - } + ).toResponse() } /// Update the password of the logged-in user. @@ -217,16 +207,14 @@ public class UserService { /// - password: The new password. /// /// - Throws: ``ResponseError`` if an error occurs. - public func updatePassword(_ password: String) -> Promise> { - return turmsClient.driver + public func updatePassword(_ password: String) async throws -> Response { + return try (await turmsClient.driver .send { $0.updateUserRequest = .with { $0.password = password } } - .map { - try $0.toResponse() - } + ).toResponse() } /// Update the profile of the logged-in user. @@ -250,27 +238,25 @@ public class UserService { name: String? = nil, intro: String? = nil, profileAccessStrategy: ProfileAccessStrategy? = nil - ) -> Promise> { + ) async throws -> Response { if Validator.areAllNil(name, intro, profileAccessStrategy) { - return Promise.value(Response.empty()) + return Response.empty() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.updateUserRequest = .with { - if let v = name { - $0.name = v + if let name { + $0.name = name } - if let v = intro { - $0.intro = v + if let intro { + $0.intro = intro } - if let v = profileAccessStrategy { - $0.profileAccessStrategy = v + if let profileAccessStrategy { + $0.profileAccessStrategy = profileAccessStrategy } } } - .map { - try $0.toResponse() - } + ).toResponse() } /// Find user profiles. @@ -284,24 +270,21 @@ public class UserService { /// - Returns: A list of user profiles. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryUserProfiles(userIds: [Int64], lastUpdatedDate: Date? = nil) -> Promise> { + public func queryUserProfiles(userIds: [Int64], lastUpdatedDate: Date? = nil) async throws -> Response<[UserInfo]> { if userIds.isEmpty { - return Promise.value(Response.emptyArray()) + return Response.emptyArray() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.queryUserProfilesRequest = .with { $0.userIds = userIds - if let v = lastUpdatedDate { - $0.lastUpdatedDate = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDate = lastUpdatedDate.toMillis() } } - } - .map { - try $0.toResponse { - $0.userInfosWithVersion.userInfos - } - } + }).toResponse { + $0.userInfosWithVersion.userInfos + } } /// Search for user profiles. @@ -319,31 +302,28 @@ public class UserService { public func searchUserProfiles(name: String, highlight: Bool = false, skip: Int32? = nil, - limit: Int32? = nil) -> Promise> + limit: Int32? = nil) async throws -> Response<[UserInfo]> { if name.isEmpty { - return Promise.value(Response.emptyArray()) + return Response.emptyArray() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.queryUserProfilesRequest = .with { $0.name = name if highlight { $0.fieldsToHighlight = [1] } - if let v = skip { - $0.skip = v + if let skip { + $0.skip = skip } - if let v = limit { - $0.limit = v + if let limit { + $0.limit = limit } } - } - .map { - try $0.toResponse { - $0.userInfosWithVersion.userInfos - } - } + }).toResponse { + $0.userInfosWithVersion.userInfos + } } /// Upsert user settings, such as "preferred language", "new message alert", etc. @@ -360,19 +340,17 @@ public class UserService { /// * If trying to update any existing immutable setting, throws ``ResponseError`` with the code ``ResponseStatusCode/illegalArgument`` /// * If trying to upsert an unknown setting and the server property `turms.service.user.settings.ignore-unknown-settings-on-upsert` is /// false (false by default), throws ``ResponseError`` with the code ``ResponseStatusCode/illegalArgument``. - public func upsertUserSettings(_ settings: [String: Value]) -> Promise> { + public func upsertUserSettings(_ settings: [String: Value]) async throws -> Response { if settings.isEmpty { - return Promise.value(Response.empty()) + return Response.empty() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.updateUserSettingsRequest = .with { $0.settings = settings } } - .map { - try $0.toResponse() - } + ).toResponse() } /// Delete user settings. @@ -386,18 +364,16 @@ public class UserService { /// /// - Throws: ``ResponseError`` if an error occurs. /// * If trying to delete any non-deletable setting, throws ``ResponseError`` with the code ``ResponseStatusCode/illegalArgument``. - public func deleteUserSettings(names: [String]? = nil) -> Promise> { - return turmsClient.driver + public func deleteUserSettings(names: [String]? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.deleteUserSettingsRequest = .with { - if let v = names { - $0.names = v + if let names { + $0.names = names } } } - .map { - try $0.toResponse() - } + ).toResponse() } /// Find user settings. @@ -408,23 +384,20 @@ public class UserService { /// The server will only return user settings if a setting has been updated after `lastUpdatedDate`. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryUserSettings(names: [String]? = nil, lastUpdatedDate: Date? = nil) -> Promise> { - return turmsClient.driver + public func queryUserSettings(names: [String]? = nil, lastUpdatedDate: Date? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.queryUserSettingsRequest = .with { - if let v = names { - $0.names = v + if let names { + $0.names = names } - if let v = lastUpdatedDate { - $0.lastUpdatedDateStart = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDateStart = lastUpdatedDate.toMillis() } } - } - .map { - try $0.toResponse { - try $0.kind?.getKindData(UserSettings.self) - } - } + }).toResponse { + try $0.kind?.getKindData(UserSettings.self) + } } /// Find nearby users. @@ -441,34 +414,31 @@ public class UserService { /// - Returns: A list of nearby users. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryNearbyUsers(latitude: Float, longitude: Float, maxCount: Int32? = nil, maxDistance: Int32? = nil, withCoordinates: Bool? = nil, withDistance: Bool? = nil, withUserInfo: Bool? = nil) -> Promise> { - return turmsClient.driver + public func queryNearbyUsers(latitude: Float, longitude: Float, maxCount: Int32? = nil, maxDistance: Int32? = nil, withCoordinates: Bool? = nil, withDistance: Bool? = nil, withUserInfo: Bool? = nil) async throws -> Response<[NearbyUser]> { + return try (await turmsClient.driver .send { $0.queryNearbyUsersRequest = .with { $0.latitude = latitude $0.longitude = longitude - if let v = maxCount { - $0.maxCount = v + if let maxCount { + $0.maxCount = maxCount } - if let v = maxDistance { - $0.maxDistance = v + if let maxDistance { + $0.maxDistance = maxDistance } - if let v = withCoordinates { - $0.withCoordinates = v + if let withCoordinates { + $0.withCoordinates = withCoordinates } - if let v = withDistance { - $0.withDistance = v + if let withDistance { + $0.withDistance = withDistance } - if let v = withUserInfo { - $0.withUserInfo = v + if let withUserInfo { + $0.withUserInfo = withUserInfo } } - } - .map { - try $0.toResponse { - $0.nearbyUsers.nearbyUsers - } - } + }).toResponse { + $0.nearbyUsers.nearbyUsers + } } /// Find online status of users. @@ -479,18 +449,15 @@ public class UserService { /// - Returns: A list of online status of users. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryUserOnlineStatusesRequest(_ userIds: [Int64]) -> Promise> { - return turmsClient.driver + public func queryUserOnlineStatusesRequest(_ userIds: [Int64]) async throws -> Response<[UserOnlineStatus]> { + return try (await turmsClient.driver .send { $0.queryUserOnlineStatusesRequest = .with { $0.userIds = userIds } - } - .map { - try $0.toResponse { - $0.userOnlineStatuses.statuses - } - } + }).toResponse { + $0.userOnlineStatuses.statuses + } } // Relationship @@ -517,29 +484,26 @@ public class UserService { isBlocked: Bool? = nil, groupIndexes: [Int32]? = nil, lastUpdatedDate: Date? = nil - ) -> Promise> { - return turmsClient.driver + ) async throws -> Response { + return try (await turmsClient.driver .send { $0.queryRelationshipsRequest = .with { - if let v = relatedUserIds { - $0.userIds = v + if let relatedUserIds { + $0.userIds = relatedUserIds } - if let v = isBlocked { - $0.blocked = v + if let isBlocked { + $0.blocked = isBlocked } - if let v = groupIndexes { - $0.groupIndexes = v + if let groupIndexes { + $0.groupIndexes = groupIndexes } - if let v = lastUpdatedDate { - $0.lastUpdatedDate = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDate = lastUpdatedDate.toMillis() } } - } - .map { - try $0.toResponse { - try $0.kind?.getKindData(UserRelationshipsWithVersion.self) - } - } + }).toResponse { + try $0.kind?.getKindData(UserRelationshipsWithVersion.self) + } } /// Find related user IDs. @@ -558,26 +522,23 @@ public class UserService { /// Note: The version can be used to update the last updated date stored locally. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryRelatedUserIds(isBlocked: Bool? = nil, groupIndexes: [Int32]? = nil, lastUpdatedDate: Date? = nil) -> Promise> { - return turmsClient.driver + public func queryRelatedUserIds(isBlocked: Bool? = nil, groupIndexes: [Int32]? = nil, lastUpdatedDate: Date? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.queryRelatedUserIdsRequest = .with { - if let v = isBlocked { - $0.blocked = v + if let isBlocked { + $0.blocked = isBlocked } - if let v = groupIndexes { - $0.groupIndexes = v + if let groupIndexes { + $0.groupIndexes = groupIndexes } - if let v = lastUpdatedDate { - $0.lastUpdatedDate = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDate = lastUpdatedDate.toMillis() } } - } - .map { - try $0.toResponse { - try $0.kind?.getKindData(LongsWithVersion.self) - } - } + }).toResponse { + try $0.kind?.getKindData(LongsWithVersion.self) + } } /// Find friends. @@ -592,8 +553,8 @@ public class UserService { /// Note: The version can be used to update the last updated date stored locally. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryFriends(groupIndexes: [Int32]? = nil, lastUpdatedDate: Date? = nil) -> Promise> { - return queryRelationships( + public func queryFriends(groupIndexes: [Int32]? = nil, lastUpdatedDate: Date? = nil) async throws -> Response { + return try await queryRelationships( isBlocked: false, groupIndexes: groupIndexes, lastUpdatedDate: lastUpdatedDate @@ -612,8 +573,8 @@ public class UserService { /// Note: The version can be used to update the last updated date stored locally. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryBlockedUsers(groupIndexes: [Int32]? = nil, lastUpdatedDate: Date? = nil) -> Promise> { - return queryRelationships( + public func queryBlockedUsers(groupIndexes: [Int32]? = nil, lastUpdatedDate: Date? = nil) async throws -> Response { + return try await queryRelationships( isBlocked: true, groupIndexes: groupIndexes, lastUpdatedDate: lastUpdatedDate @@ -637,20 +598,18 @@ public class UserService { /// If null, the relationship will be created in the default group. /// /// - Throws: ``ResponseError`` if an error occurs. - public func createRelationship(userId: Int64, isBlocked: Bool, groupIndex: Int32? = nil) -> Promise> { - return turmsClient.driver + public func createRelationship(userId: Int64, isBlocked: Bool, groupIndex: Int32? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.createRelationshipRequest = .with { $0.userID = userId $0.blocked = isBlocked - if let v = groupIndex { - $0.groupIndex = v + if let groupIndex { + $0.groupIndex = groupIndex } } } - .map { - try $0.toResponse() - } + ).toResponse() } /// Create a friend (non-blocked) relationship. @@ -667,8 +626,8 @@ public class UserService { /// If null, the relationship will be created in the default group. /// /// - Throws: ``ResponseError`` if an error occurs. - public func createFriendRelationship(userId: Int64, groupIndex: Int32? = nil) -> Promise> { - return createRelationship( + public func createFriendRelationship(userId: Int64, groupIndex: Int32? = nil) async throws -> Response { + return try await createRelationship( userId: userId, isBlocked: false, groupIndex: groupIndex @@ -689,8 +648,8 @@ public class UserService { /// If null, the relationship will be created in the default group. /// /// - Throws: ``ResponseError`` if an error occurs. - public func createBlockedUserRelationship(userId: Int64, groupIndex: Int32? = nil) -> Promise> { - return createRelationship( + public func createBlockedUserRelationship(userId: Int64, groupIndex: Int32? = nil) async throws -> Response { + return try await createRelationship( userId: userId, isBlocked: true, groupIndex: groupIndex @@ -712,22 +671,20 @@ public class UserService { /// - targetGroupIndex: TODO: not implemented yet. /// /// - Throws: ``ResponseError`` if an error occurs. - public func deleteRelationship(relatedUserId: Int64, deleteGroupIndex: Int32? = nil, targetGroupIndex: Int32? = nil) -> Promise> { - return turmsClient.driver + public func deleteRelationship(relatedUserId: Int64, deleteGroupIndex: Int32? = nil, targetGroupIndex: Int32? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.deleteRelationshipRequest = .with { $0.userID = relatedUserId - if let v = deleteGroupIndex { - $0.groupIndex = v + if let deleteGroupIndex { + $0.groupIndex = deleteGroupIndex } - if let v = targetGroupIndex { - $0.targetGroupIndex = v + if let targetGroupIndex { + $0.targetGroupIndex = targetGroupIndex } } } - .map { - try $0.toResponse() - } + ).toResponse() } /// Update a relationship. @@ -744,25 +701,23 @@ public class UserService { /// If null, the relationship will not be updated. /// /// - Throws: ``ResponseError`` if an error occurs. - public func updateRelationship(relatedUserId: Int64, isBlocked: Bool? = nil, groupIndex: Int32? = nil) -> Promise> { + public func updateRelationship(relatedUserId: Int64, isBlocked: Bool? = nil, groupIndex: Int32? = nil) async throws -> Response { if Validator.areAllNil(isBlocked, groupIndex) { - return Promise.value(Response.empty()) + return Response.empty() } - return turmsClient.driver + return try (await turmsClient.driver .send { $0.updateRelationshipRequest = .with { $0.userID = relatedUserId - if let v = isBlocked { - $0.blocked = v + if let isBlocked { + $0.blocked = isBlocked } - if let v = groupIndex { - $0.newGroupIndex = v + if let groupIndex { + $0.newGroupIndex = groupIndex } } } - .map { - try $0.toResponse() - } + ).toResponse() } /// Send a friend request. @@ -780,19 +735,16 @@ public class UserService { /// - Returns: The request ID. /// /// - Throws: ``ResponseError`` if an error occurs. - public func sendFriendRequest(recipientId: Int64, content: String) -> Promise> { - return turmsClient.driver + public func sendFriendRequest(recipientId: Int64, content: String) async throws -> Response { + return try (await turmsClient.driver .send { $0.createFriendRequestRequest = .with { $0.recipientID = recipientId $0.content = content } - } - .map { - try $0.toResponse { - try $0.getLongOrThrow() - } - } + }).toResponse { + try $0.getLongOrThrow() + } } /// Delete/Recall a friend request. @@ -813,16 +765,14 @@ public class UserService { /// is true (true by default), the server will send a delete friend request notification to the recipient of the friend request actively. /// /// - Throws: ``ResponseError`` if an error occurs. - public func deleteFriendRequest(_ requestId: Int64) -> Promise> { - return turmsClient.driver + public func deleteFriendRequest(_ requestId: Int64) async throws -> Response { + return try (await turmsClient.driver .send { $0.deleteFriendRequestRequest = .with { $0.requestID = requestId } } - .map { - try $0.toResponse() - } + ).toResponse() } /// Reply to a friend request. @@ -848,20 +798,18 @@ public class UserService { /// - reason: The reason of the response. /// /// - Throws: ``ResponseError`` if an error occurs. - public func replyFriendRequest(requestId: Int64, responseAction: ResponseAction, reason: String? = nil) -> Promise> { - return turmsClient.driver + public func replyFriendRequest(requestId: Int64, responseAction: ResponseAction, reason: String? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.updateFriendRequestRequest = .with { $0.requestID = requestId $0.responseAction = responseAction - if let v = reason { - $0.reason = v + if let reason { + $0.reason = reason } } } - .map { - try $0.toResponse() - } + ).toResponse() } /// Find friend requests. @@ -878,21 +826,18 @@ public class UserService { /// Note: The version can be used to update the last updated date stored locally. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryFriendRequests(_ areSentByMe: Bool, lastUpdatedDate: Date? = nil) -> Promise> { - return turmsClient.driver + public func queryFriendRequests(_ areSentByMe: Bool, lastUpdatedDate: Date? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.queryFriendRequestsRequest = .with { $0.areSentByMe = areSentByMe - if let v = lastUpdatedDate { - $0.lastUpdatedDate = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDate = lastUpdatedDate.toMillis() } } - } - .map { - try $0.toResponse { - try $0.kind?.getKindData(UserFriendRequestsWithVersion.self) - } - } + }).toResponse { + try $0.kind?.getKindData(UserFriendRequestsWithVersion.self) + } } /// Create a relationship group. @@ -903,18 +848,15 @@ public class UserService { /// - Returns: The index of the created group. /// /// - Throws: ``ResponseError`` if an error occurs. - public func createRelationshipGroup(_ name: String) -> Promise> { - return turmsClient.driver + public func createRelationshipGroup(_ name: String) async throws -> Response { + return try (await turmsClient.driver .send { $0.createRelationshipGroupRequest = .with { $0.name = name } - } - .map { - try $0.toResponse { - try Int32($0.getLongOrThrow()) - } - } + }).toResponse { + try Int32($0.getLongOrThrow()) + } } /// Delete relationship groups. @@ -932,19 +874,17 @@ public class UserService { /// If null, the group members of `groupIndex` will be moved to the default group. /// /// - Throws: ``ResponseError`` if an error occurs. - public func deleteRelationshipGroups(groupIndex: Int32, targetGroupIndex: Int32? = nil) -> Promise> { - return turmsClient.driver + public func deleteRelationshipGroups(groupIndex: Int32, targetGroupIndex: Int32? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.deleteRelationshipGroupRequest = .with { $0.groupIndex = groupIndex - if let v = targetGroupIndex { - $0.targetGroupIndex = v + if let targetGroupIndex { + $0.targetGroupIndex = targetGroupIndex } } } - .map { - try $0.toResponse() - } + ).toResponse() } /// Update a relationship group. @@ -960,17 +900,15 @@ public class UserService { /// - newName: The new name of the group. /// /// - Throws: ``ResponseError`` if an error occurs. - public func updateRelationshipGroup(groupIndex: Int32, newName: String) -> Promise> { - return turmsClient.driver + public func updateRelationshipGroup(groupIndex: Int32, newName: String) async throws -> Response { + return try (await turmsClient.driver .send { $0.updateRelationshipGroupRequest = .with { $0.groupIndex = groupIndex $0.newName = newName } } - .map { - try $0.toResponse() - } + ).toResponse() } /// Find relationship groups. @@ -984,20 +922,17 @@ public class UserService { /// Note: The version can be used to update the last updated date stored locally. /// /// - Throws: ``ResponseError`` if an error occurs. - public func queryRelationshipGroups(_ lastUpdatedDate: Date? = nil) -> Promise> { - return turmsClient.driver + public func queryRelationshipGroups(_ lastUpdatedDate: Date? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.queryRelationshipGroupsRequest = .with { - if let v = lastUpdatedDate { - $0.lastUpdatedDate = v.toMillis() + if let lastUpdatedDate { + $0.lastUpdatedDate = lastUpdatedDate.toMillis() } } - } - .map { - try $0.toResponse { - try $0.kind?.getKindData(UserRelationshipGroupsWithVersion.self) - } - } + }).toResponse { + try $0.kind?.getKindData(UserRelationshipGroupsWithVersion.self) + } } /// Move a related user to a group. @@ -1013,17 +948,15 @@ public class UserService { /// - groupIndex: The target group index to which move the user. /// /// - Throws: ``ResponseError`` if an error occurs. - public func moveRelatedUserToGroup(relatedUserId: Int64, groupIndex: Int32) -> Promise> { - return turmsClient.driver + public func moveRelatedUserToGroup(relatedUserId: Int64, groupIndex: Int32) async throws -> Response { + return try (await turmsClient.driver .send { $0.updateRelationshipRequest = .with { $0.userID = relatedUserId $0.newGroupIndex = groupIndex } } - .map { - try $0.toResponse() - } + ).toResponse() } /// Update the location of the logged-in user. @@ -1042,20 +975,18 @@ public class UserService { /// - details: The location details /// /// - Throws: ``ResponseError`` if an error occurs. - public func updateLocation(latitude: Float, longitude: Float, details: [String: String]? = nil) -> Promise> { - return turmsClient.driver + public func updateLocation(latitude: Float, longitude: Float, details: [String: String]? = nil) async throws -> Response { + return try (await turmsClient.driver .send { $0.updateUserLocationRequest = .with { $0.latitude = latitude $0.longitude = longitude - if let v = details { - $0.details = v + if let details { + $0.details = details } } } - .map { - try $0.toResponse() - } + ).toResponse() } private func changeToOnline() { diff --git a/turms-client-swift/Sources/TurmsClient/Transport/MultipartFormDataRequest.swift b/turms-client-swift/Sources/TurmsClient/Transport/MultipartFormDataRequest.swift index e1b171a785..86b9fb9781 100644 --- a/turms-client-swift/Sources/TurmsClient/Transport/MultipartFormDataRequest.swift +++ b/turms-client-swift/Sources/TurmsClient/Transport/MultipartFormDataRequest.swift @@ -35,7 +35,7 @@ struct MultipartFormDataRequest { let fieldData = NSMutableData() fieldData.appendString("--\(boundary)\r\n") fieldData.appendString("Content-Disposition: form-data; name=\"\(fieldName)\"; filename=\"\(fileName)\"\r\n") - if let mediaType = mediaType { + if let mediaType { fieldData.appendString("Content-Type: \(mediaType)\r\n") } fieldData.appendString("\r\n") diff --git a/turms-client-swift/Sources/TurmsClient/Transport/TcpClient.swift b/turms-client-swift/Sources/TurmsClient/Transport/TcpClient.swift index 81a1867303..6bf72e5ee3 100644 --- a/turms-client-swift/Sources/TurmsClient/Transport/TcpClient.swift +++ b/turms-client-swift/Sources/TurmsClient/Transport/TcpClient.swift @@ -1,6 +1,5 @@ import Foundation import Network -import PromiseKit public enum TCPTransportError: Error { case invalidRequest @@ -8,7 +7,7 @@ public enum TCPTransportError: Error { public class TcpClient { private var connection: NWConnection? - private let queue = DispatchQueue(label: "im.turms.turmsclient.transport", attributes: []) + private let queue = DispatchQueue(label: "im.turms.turmsclient.transport") private var isOpen = false private var metrics = TcpMetrics() @@ -20,8 +19,8 @@ public class TcpClient { self.onDataReceived = onDataReceived } - public func connect(host: String, port: UInt16, useTls: Bool = false, timeout: Double? = nil, certificatePinning: CertificatePinning? = nil) -> Promise { - return Promise { seal in + public func connect(host: String, port: UInt16, useTls: Bool = false, timeout: Double? = nil, certificatePinning: CertificatePinning? = nil) async throws { + return try await withUnsafeThrowingContinuation { continuation in let options = NWProtocolTCP.Options() if let t = timeout { options.connectionTimeout = Int(t.rounded(.up)) @@ -49,9 +48,8 @@ public class TcpClient { } let parameters = NWParameters(tls: tlsOptions, tcp: options) connection = NWConnection(host: NWEndpoint.Host.name(host, nil), port: NWEndpoint.Port(rawValue: port)!, using: parameters) - guard let connection = connection else { - seal.reject(RuntimeError("Connection is nil unexpectedly")) - return + guard let connection else { + return continuation.resume(throwing: RuntimeError("Connection is nil unexpectedly")) } connection.stateUpdateHandler = { [weak self] newState in guard let s = self else { @@ -59,30 +57,23 @@ public class TcpClient { } switch newState { case .ready: - if #available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) { - connection.requestEstablishmentReport(queue: s.queue) { report in - guard let report = report else { - s.isOpen = true - s.readLoop() - seal.fulfill_() - return - } - s.metrics.connectTime = report.handshakes.max { handshake1, handshake2 in handshake1.handshakeDuration < handshake2.handshakeDuration }?.handshakeDuration - s.metrics.addressResolverTime = report.resolutions.max { resolution1, resolution2 in resolution1.duration < resolution2.duration }?.duration + connection.requestEstablishmentReport(queue: s.queue) { report in + guard let report else { s.isOpen = true s.readLoop() - seal.fulfill_() + return continuation.resume() } - } else { + s.metrics.connectTime = report.handshakes.max { handshake1, handshake2 in handshake1.handshakeDuration < handshake2.handshakeDuration }?.handshakeDuration + s.metrics.addressResolverTime = report.resolutions.max { resolution1, resolution2 in resolution1.duration < resolution2.duration }?.duration s.isOpen = true s.readLoop() - seal.fulfill_() + continuation.resume() } case .cancelled: - seal.reject(RuntimeError("\(newState)")) + continuation.resume(throwing: RuntimeError("\(newState)")) s.onClose() case let .failed(error): - seal.reject(RuntimeError("\(newState)")) + continuation.resume(throwing: RuntimeError("\(newState)")) s.onClose(error) case .waiting, .setup, .preparing: break @@ -98,11 +89,14 @@ public class TcpClient { if !isOpen { return } - connection!.receive(minimumIncompleteLength: 1, maximumLength: 4096, completion: { [weak self] data, context, isComplete, error in + guard let connection else { + return + } + connection.receive(minimumIncompleteLength: 1, maximumLength: 4096, completion: { [weak self] data, context, isComplete, error in guard let s = self else { return } - if let data = data { + if let data { s.metrics.dataReceived += data.count do { try s.onDataReceived(data) @@ -111,7 +105,7 @@ public class TcpClient { return } } - if let context = context, context.isFinal, isComplete { + if let context, context.isFinal, isComplete { s.close() return } @@ -123,24 +117,30 @@ public class TcpClient { }) } - public func write(_ data: Data, _ completion: ((_ error: NWError?) -> Void)? = nil) { - connection!.send(content: data, completion: .contentProcessed { [weak self] error in - guard let s = self else { - return - } - if let e = error { - s.close(e) - } else { - s.metrics.dataSent += data.count - } - completion?(error) - }) + public func write(_ data: Data) async throws { + guard let connection else { + throw RuntimeError("The connection is not open") + } + return try await withUnsafeThrowingContinuation { continuation in + connection.send(content: data, completion: .contentProcessed { [weak self] error in + guard let s = self else { + return + } + if let e = error { + s.close(e) + continuation.resume(throwing: e) + } else { + s.metrics.dataSent += data.count + continuation.resume() + } + }) + } } - public func writeVarIntLengthAndBytes(_ data: Data, completion: ((_ error: NWError?) -> Void)? = nil) { + public func writeVarIntLengthAndBytes(_ data: Data) async throws { var list = encodeVarInt(data.count) list.append(data) - write(list, completion) + try await write(list) } private func encodeVarInt(_ value: Int) -> Data { @@ -165,13 +165,9 @@ public class TcpClient { private func onClose(_ error: Error? = nil) { if isOpen { - do { - onClosed(error) - } catch { - // TODO: log - } + onClosed(error) } metrics = TcpMetrics() isOpen = false } -} +} \ No newline at end of file diff --git a/turms-client-swift/Sources/TurmsClient/TurmsClient.swift b/turms-client-swift/Sources/TurmsClient/TurmsClient.swift index 87708c6865..4441d584cf 100755 --- a/turms-client-swift/Sources/TurmsClient/TurmsClient.swift +++ b/turms-client-swift/Sources/TurmsClient/TurmsClient.swift @@ -1,5 +1,4 @@ import Foundation -import PromiseKit public class TurmsClient { public private(set) var driver: TurmsDriver @@ -29,7 +28,7 @@ public class TurmsClient { notificationService = NotificationService(self) } - public func close() -> Promise { - return driver.close() + public func close() async { + await driver.close() } } diff --git a/turms-client-swift/Sources/TurmsClient/Util/Lock.swift b/turms-client-swift/Sources/TurmsClient/Util/Lock.swift new file mode 100644 index 0000000000..363dbd4db2 --- /dev/null +++ b/turms-client-swift/Sources/TurmsClient/Util/Lock.swift @@ -0,0 +1,20 @@ +import Foundation + +final class Lock { + private var lock: UnsafeMutablePointer + + init() { + lock = .allocate(capacity: 1) + lock.initialize(to: os_unfair_lock()) + } + + deinit { + lock.deallocate() + } + + func locked(_ f: () throws -> T) rethrows -> T { + os_unfair_lock_lock(lock) + defer { os_unfair_lock_unlock(lock) } + return try f() + } +} diff --git a/turms-client-swift/Tests/TurmsClientTests/Driver/TurmsDriverTests.swift b/turms-client-swift/Tests/TurmsClientTests/Driver/TurmsDriverTests.swift index c196439a35..69a6b3a7e3 100755 --- a/turms-client-swift/Tests/TurmsClientTests/Driver/TurmsDriverTests.swift +++ b/turms-client-swift/Tests/TurmsClientTests/Driver/TurmsDriverTests.swift @@ -2,17 +2,17 @@ import XCTest class TurmsDriverTests: XCTestCase { - func test_system() { + func test_system() async throws { let client = TurmsClient(Config.HOST, Config.PORT) let driver = client.driver - assertCompleted("connect_shouldSucceed", driver.connect()) - assertCompleted("login_shouldSucceed", client.userService.login(userId: 1, password: "123")) - assertCompleted("sendHeartbeat_shouldSucceed", driver.sendHeartbeat()) - assertCompleted("sendTurmsRequest_shouldSucceed", driver.send { + try await assertCompleted("connect_shouldSucceed", await driver.connect()) + try await assertCompleted("login_shouldSucceed", await client.userService.login(userId: 1, password: "123")) + try await assertCompleted("sendHeartbeat_shouldSucceed", await driver.sendHeartbeat()) + try await assertCompleted("sendTurmsRequest_shouldSucceed", await driver.send { $0.queryUserProfilesRequest = .with { $0.userIds = [1] } }) - assertCompleted("disconnect_shouldSucceed", driver.disconnect()) + try await assertCompleted("disconnect_shouldSucceed", await driver.disconnect()) } } diff --git a/turms-client-swift/Tests/TurmsClientTests/Helper/XCTestCase+Additions.swift b/turms-client-swift/Tests/TurmsClientTests/Helper/XCTestCase+Additions.swift index 778cfa41ac..7c531a65ef 100644 --- a/turms-client-swift/Tests/TurmsClientTests/Helper/XCTestCase+Additions.swift +++ b/turms-client-swift/Tests/TurmsClientTests/Helper/XCTestCase+Additions.swift @@ -1,27 +1,42 @@ import Foundation -import PromiseKit + import TurmsClient import XCTest extension XCTestCase { - func assertCompleted(_ description: String, _ promise: Promise) { + func assertCompleted(_ description: String, _ taskProvider: @autoclosure @escaping () async throws -> T) async throws { + return try await assertCompleted(description, taskProvider) + } + + func assertCompleted(_ description: String, _ taskProvider: @escaping () async throws -> T) async throws { let e = expectation(description: description) - promise.done { _ in - e.fulfill() - }.catch { error in - XCTFail("Failed: \(description): \(error)") + let task = Task { + do { + let result = try await taskProvider() + e.fulfill() + } catch { + XCTFail("Failed: \(description): \(error)") + } } - let result = XCTWaiter().wait(for: [e], timeout: 5) - XCTAssertEqual(.completed, result, "Failed: \(description): \(result)") + await fulfillment(of: [e], timeout: 5) + task.cancel() + } + + func wait(_ taskProvider: @autoclosure @escaping () async throws -> T) async throws { + return try await wait(taskProvider) } - func wait(_ promise: Promise) -> XCTWaiter.Result { + func wait(_ taskProvider: @escaping () async throws -> T) async throws { let e = expectation(description: "") - promise.done { _ in - e.fulfill() - }.catch { error in - XCTFail("Failed: \(error)") + let task = Task { + do { + let result = try await taskProvider() + e.fulfill() + } catch { + XCTFail("Failed: \(error)") + } } - return XCTWaiter().wait(for: [e], timeout: 5) + await fulfillment(of: [e], timeout: 5) + task.cancel() } } diff --git a/turms-client-swift/Tests/TurmsClientTests/Service/ConversationServiceTests.swift b/turms-client-swift/Tests/TurmsClientTests/Service/ConversationServiceTests.swift index ee2d63a288..4516f858b3 100644 --- a/turms-client-swift/Tests/TurmsClientTests/Service/ConversationServiceTests.swift +++ b/turms-client-swift/Tests/TurmsClientTests/Service/ConversationServiceTests.swift @@ -1,35 +1,36 @@ -import PromiseKit @testable import TurmsClient import XCTest class ConversationServiceTests: XCTestCase { - static let USER_ID: Int64 = 1 - static let RELATED_USER_ID: Int64 = 2 - static let GROUP_ID: Int64 = 1 - var turmsClient: TurmsClient! + private static let USER_ID: Int64 = 1 + private static let RELATED_USER_ID: Int64 = 2 + private static let GROUP_ID: Int64 = 1 + private var turmsClient: TurmsClient! - func test_e2e() { + func test_e2e() async throws { // Set up continueAfterFailure = false turmsClient = TurmsClient(Config.HOST, Config.PORT) - wait(turmsClient.userService.login(userId: ConversationServiceTests.USER_ID, password: "123")) + try await wait(await self.turmsClient.userService.login(userId: ConversationServiceTests.USER_ID, password: "123")) // Update let service = turmsClient.conversationService! - assertCompleted("updatePrivateConversationReadDate_shouldSucceed", service.updatePrivateConversationReadDate(ConversationServiceTests.RELATED_USER_ID)) - assertCompleted("updateGroupConversationReadDate_shouldSucceed", service.updateGroupConversationReadDate(ConversationServiceTests.GROUP_ID)) - assertCompleted("updatePrivateConversationTypingStatus_shouldSucceed", service.updatePrivateConversationTypingStatus(ConversationServiceTests.RELATED_USER_ID)) - assertCompleted("updateGroupConversationTypingStatus_shouldSucceed", service.updateGroupConversationTypingStatus(ConversationServiceTests.GROUP_ID)) + try await assertCompleted("updatePrivateConversationReadDate_shouldSucceed", await service.updatePrivateConversationReadDate(ConversationServiceTests.RELATED_USER_ID)) + try await assertCompleted("updateGroupConversationReadDate_shouldSucceed", await service.updateGroupConversationReadDate(ConversationServiceTests.GROUP_ID)) + try await assertCompleted("updatePrivateConversationTypingStatus_shouldSucceed", await service.updatePrivateConversationTypingStatus(ConversationServiceTests.RELATED_USER_ID)) + try await assertCompleted("updateGroupConversationTypingStatus_shouldSucceed", await service.updateGroupConversationTypingStatus(ConversationServiceTests.GROUP_ID)) // Query - assertCompleted("queryPrivateConversations_shouldReturnNotEmptyConversations", service.queryPrivateConversations([ConversationServiceTests.RELATED_USER_ID]).done { - XCTAssertFalse($0.data.isEmpty) - }) - assertCompleted("queryGroupConversations_shouldReturnNotEmptyConversations", service.queryGroupConversations([ConversationServiceTests.GROUP_ID]).done { - XCTAssertFalse($0.data.isEmpty) - }) + try await assertCompleted("queryPrivateConversations_shouldReturnNotEmptyConversations") { + let conversations = try await service.queryPrivateConversations([ConversationServiceTests.RELATED_USER_ID]) + XCTAssertFalse(conversations.data.isEmpty) + } + try await assertCompleted("queryGroupConversations_shouldReturnNotEmptyConversations") { + let conversations = try await service.queryGroupConversations([ConversationServiceTests.GROUP_ID]) + XCTAssertFalse(conversations.data.isEmpty) + } // Tear down - wait(turmsClient.driver.disconnect()) + try await wait(await self.turmsClient.driver.disconnect()) } } diff --git a/turms-client-swift/Tests/TurmsClientTests/Service/GroupServiceTests.swift b/turms-client-swift/Tests/TurmsClientTests/Service/GroupServiceTests.swift index 0998a3dabe..a4789b6bc6 100755 --- a/turms-client-swift/Tests/TurmsClientTests/Service/GroupServiceTests.swift +++ b/turms-client-swift/Tests/TurmsClientTests/Service/GroupServiceTests.swift @@ -1,21 +1,20 @@ -import PromiseKit @testable import TurmsClient import XCTest class GroupServiceTests: XCTestCase { - var turmsClient: TurmsClient! + private var turmsClient: TurmsClient! - override func setUp() { + override func setUp() async throws { continueAfterFailure = false turmsClient = TurmsClient(Config.HOST, Config.PORT) - wait(turmsClient.userService.login(userId: 1, password: "123")) + try await wait(await self.turmsClient.userService.login(userId: 1, password: "123")) } - override func tearDown() { - wait(turmsClient.userService.logout()) + override func tearDown() async throws { + try await wait(await self.turmsClient.userService.logout()) } - func test_e2e() { + func test_e2e() async throws { let groupMemberId: Int64 = 3 let groupInvitationInviteeId: Int64 = 4 let groupSuccessorId: Int64 = 1 @@ -28,99 +27,120 @@ class GroupServiceTests: XCTestCase { let service = turmsClient.groupService! // Create - assertCompleted("createGroup_shouldReturnGroupId", service.createGroup(name: "name", intro: "intro", announcement: "announcement", minScore: 10).done { - groupId = $0.data - }) - assertCompleted("addGroupJoinQuestions_shouldReturnQuestionIds", service.addGroupJoinQuestions(groupId: groupId!, questions: [NewGroupJoinQuestion(question: "question", answers: ["answer1", "answer2"], score: 10)]).done { - groupQuestionId = $0.data[0] - }) - assertCompleted("createJoinRequest_shouldReturnJoinRequestId", service.createJoinRequest(groupId: groupId!, content: "content").done { - groupJoinRequestId = $0.data - }) - assertCompleted("addGroupMembers_shouldSucceed", service.addGroupMembers(groupId: groupId!, userIds: [groupMemberId], name: "name", role: .member)) - assertCompleted("blockUser_shouldSucceed", service.blockUser(groupId: groupId!, userId: groupBlockedUserId)) - assertCompleted("createInvitation_shouldReturnInvitationId", service.createInvitation(groupId: groupId!, inviteeId: groupInvitationInviteeId, content: "content").done { - groupInvitationId = $0.data - }) + try await assertCompleted("createGroup_shouldReturnGroupId") { + let result = try await service.createGroup(name: "name", intro: "intro", announcement: "announcement", minScore: 10) + groupId = result.data + } + try await assertCompleted("addGroupJoinQuestions_shouldReturnQuestionIds") { + let result = try await service.addGroupJoinQuestions(groupId: groupId!, questions: [NewGroupJoinQuestion(question: "question", answers: ["answer1", "answer2"], score: 10)]) + groupQuestionId = result.data[0] + } + try await assertCompleted("createJoinRequest_shouldReturnJoinRequestId") { + let result = try await service.createJoinRequest(groupId: groupId!, content: "content") + groupJoinRequestId = result.data + } + try await assertCompleted("addGroupMembers_shouldSucceed", await service.addGroupMembers(groupId: groupId!, userIds: [groupMemberId], name: "name", role: .member)) + try await assertCompleted("blockUser_shouldSucceed", await service.blockUser(groupId: groupId!, userId: groupBlockedUserId)) + try await assertCompleted("createInvitation_shouldReturnInvitationId") { + let result = try await service.createInvitation(groupId: groupId!, inviteeId: groupInvitationInviteeId, content: "content") + groupInvitationId = result.data + } // Update - assertCompleted("updateGroup_shouldSucceed", service.updateGroup(groupId: groupId!, name: "name", intro: "intro", announcement: "announcement", minScore: 10)) - assertCompleted("muteGroup_shouldSucceed", service.muteGroup(groupId: groupId!, muteEndDate: Date())) - assertCompleted("unmuteGroup_shouldSucceed", service.unmuteGroup(groupId!)) - assertCompleted("updateGroupJoinQuestion_shouldSucceed", service.updateGroupJoinQuestion(questionId: groupQuestionId!, question: "new-question", answers: ["answer"])) - assertCompleted("updateGroupMemberInfo_shouldSucceed", service.updateGroupMemberInfo(groupId: groupId!, memberId: groupMemberId, name: "myname")) - assertCompleted("muteGroupMember_shouldSucceed", service.muteGroupMember(groupId: groupId!, memberId: groupMemberId, muteEndDate: Date(timeIntervalSinceNow: 100_000))) - assertCompleted("unmuteGroupMember_shouldSucceed", service.unmuteGroupMember(groupId: groupId!, memberId: groupMemberId)) + try await assertCompleted("updateGroup_shouldSucceed", await service.updateGroup(groupId: groupId!, name: "name", intro: "intro", announcement: "announcement", minScore: 10)) + try await assertCompleted("muteGroup_shouldSucceed", await service.muteGroup(groupId: groupId!, muteEndDate: Date())) + try await assertCompleted("unmuteGroup_shouldSucceed", await service.unmuteGroup(groupId!)) + try await assertCompleted("updateGroupJoinQuestion_shouldSucceed", await service.updateGroupJoinQuestion(questionId: groupQuestionId!, question: "new-question", answers: ["answer"])) + try await assertCompleted("updateGroupMemberInfo_shouldSucceed", await service.updateGroupMemberInfo(groupId: groupId!, memberId: groupMemberId, name: "myname")) + try await assertCompleted("muteGroupMember_shouldSucceed", await service.muteGroupMember(groupId: groupId!, memberId: groupMemberId, muteEndDate: Date(timeIntervalSinceNow: 100_000))) + try await assertCompleted("unmuteGroupMember_shouldSucceed", await service.unmuteGroupMember(groupId: groupId!, memberId: groupMemberId)) // Query - assertCompleted("queryGroups_shouldReturnGroups", service.queryGroups(groupIds: [groupId!]).done { - XCTAssertEqual(groupId!, $0.data[0].id) - }) - assertCompleted("queryJoinedGroupIds_shouldEqualNewGroupId", service.queryJoinedGroupIds().done { - XCTAssert($0.data!.longs.contains(groupId!)) - }) - assertCompleted("queryJoinedGroupInfos_shouldEqualNewGroupId", service.queryJoinedGroupInfos().done { - let groupIds = $0.data!.groups.map { + try await assertCompleted("queryGroups_shouldReturnGroups") { + let groups = try await service.queryGroups(groupIds: [groupId!]) + XCTAssertEqual(groupId!, groups.data[0].id) + } + try await assertCompleted("queryJoinedGroupIds_shouldEqualNewGroupId") { + let joinedGroupIds = try await service.queryJoinedGroupIds() + XCTAssert(joinedGroupIds.data!.longs.contains(groupId!)) + } + try await assertCompleted("queryJoinedGroupInfos_shouldEqualNewGroupId") { + let joinedGroupInfos = try await service.queryJoinedGroupInfos() + let groupIds = joinedGroupInfos.data!.groups.map { $0.id } XCTAssert(groupIds.contains(groupId!)) - }) - assertCompleted("queryBlockedUserIds_shouldEqualBlockedUserId", service.queryBlockedUserIds(groupId: groupId!).done { - XCTAssertEqual(groupBlockedUserId, $0.data!.longs[0]) - }) - assertCompleted("queryBlockedUserInfos_shouldEqualBlockedUserId", service.queryBlockedUserInfos(groupId: groupId!).done { - XCTAssertEqual(groupBlockedUserId, $0.data!.userInfos[0].id) - }) - assertCompleted("queryInvitations_shouldEqualNewInvitationId", service.queryInvitations(groupId: groupId!).done { - XCTAssertEqual(groupInvitationId, $0.data!.groupInvitations[0].id) - }) - assertCompleted("queryJoinRequests_shouldEqualNewJoinRequestId", service.queryJoinRequests(groupId: groupId!).done { - XCTAssertEqual(groupJoinRequestId, $0.data!.groupJoinRequests[0].id) - }) - assertCompleted("queryGroupJoinQuestions_shouldEqualNewGroupQuestionId", service.queryGroupJoinQuestions(groupId: groupId!, withAnswers: true).done { - XCTAssertEqual(groupQuestionId, $0.data!.groupJoinQuestions[0].id) - }) - assertCompleted("queryGroupMembers_shouldEqualNewMemberId", service.queryGroupMembers(groupId: groupId!, withStatus: true).done { - XCTAssertEqual(groupMemberId, $0.data!.groupMembers[1].userID) - }) - assertCompleted("queryGroupMembersByMemberIds_shouldEqualNewMemberId", service.queryGroupMembersByMemberIds(groupId: groupId!, memberIds: [groupMemberId], withStatus: true).done { - XCTAssertEqual(groupMemberId, $0.data!.groupMembers[0].userID) - }) - assertCompleted("answerGroupQuestions_shouldReturnAnswerResult", service.answerGroupQuestions([groupQuestionId!: "answer"]).recover { error -> Promise> in - if let businessError = error as? ResponseError, businessError.code == ResponseStatusCode.groupMemberAnswerGroupQuestion.rawValue { - return Promise.value(Response.value(GroupJoinQuestionsAnswerResult())) - } else { - throw error + } + try await assertCompleted("queryBlockedUserIds_shouldEqualBlockedUserId") { + let blockedUserIds = try await service.queryBlockedUserIds(groupId: groupId!) + XCTAssertEqual(groupBlockedUserId, blockedUserIds.data!.longs[0]) + } + try await assertCompleted("queryBlockedUserInfos_shouldEqualBlockedUserId") { + let blockedUserInfos = try await service.queryBlockedUserInfos(groupId: groupId!) + XCTAssertEqual(groupBlockedUserId, blockedUserInfos.data!.userInfos[0].id) + } + try await assertCompleted("queryInvitations_shouldEqualNewInvitationId") { + let invitations = try await service.queryInvitations(groupId: groupId!) + XCTAssertEqual(groupInvitationId, invitations.data!.groupInvitations[0].id) + } + try await assertCompleted("queryJoinRequests_shouldEqualNewJoinRequestId") { + let joinRequests = try await service.queryJoinRequests(groupId: groupId!) + XCTAssertEqual(groupJoinRequestId, joinRequests.data!.groupJoinRequests[0].id) + } + try await assertCompleted("queryGroupJoinQuestions_shouldEqualNewGroupQuestionId") { + let groupJoinQuestions = try await service.queryGroupJoinQuestions(groupId: groupId!, withAnswers: true) + XCTAssertEqual(groupQuestionId, groupJoinQuestions.data!.groupJoinQuestions[0].id) + } + try await assertCompleted("queryGroupMembers_shouldEqualNewMemberId") { + let groupMembers = try await service.queryGroupMembers(groupId: groupId!, withStatus: true) + XCTAssertEqual(groupMemberId, groupMembers.data!.groupMembers[1].userID) + } + try await assertCompleted("queryGroupMembersByMemberIds_shouldEqualNewMemberId") { + let groupMembers = try await service.queryGroupMembersByMemberIds(groupId: groupId!, memberIds: [groupMemberId], withStatus: true) + XCTAssertEqual(groupMemberId, groupMembers.data!.groupMembers[0].userID) + } + try await assertCompleted("answerGroupQuestions_shouldReturnAnswerResult") { + do { + let result = try await service.answerGroupQuestions([groupQuestionId!: "answer"]) + } catch { + guard let businessError = error as? ResponseError, businessError.code == ResponseStatusCode.groupMemberAnswerGroupQuestion.rawValue else { + throw error + } } - }) + } // Delete - assertCompleted("removeGroupMember_shouldSucceed", service.removeGroupMember(groupId: groupId!, memberId: groupMemberId)) - assertCompleted("deleteGroupJoinQuestions_shouldSucceed", service.deleteGroupJoinQuestions(groupId: groupId!, questionIds: [groupQuestionId!])) - assertCompleted("unblockUser_shouldSucceed", service.unblockUser(groupId: groupId!, userId: groupBlockedUserId)) - assertCompleted("deleteInvitation_shouldSucceedOrThrowDisabledFunction", service.deleteInvitation(groupInvitationId!).recover { error -> Promise> in - if let businessError = error as? ResponseError, businessError.code == ResponseStatusCode.recallingGroupInvitationIsDisabled.rawValue { - return Promise.value(Response.empty()) - } else { - throw error + try await assertCompleted("removeGroupMember_shouldSucceed", await service.removeGroupMember(groupId: groupId!, memberId: groupMemberId)) + try await assertCompleted("deleteGroupJoinQuestions_shouldSucceed", await service.deleteGroupJoinQuestions(groupId: groupId!, questionIds: [groupQuestionId!])) + try await assertCompleted("unblockUser_shouldSucceed", await service.unblockUser(groupId: groupId!, userId: groupBlockedUserId)) + try await assertCompleted("deleteInvitation_shouldSucceedOrThrowDisabledFunction") { + do { + let result = try await service.deleteInvitation(groupInvitationId!) + } catch { + guard let businessError = error as? ResponseError, businessError.code == ResponseStatusCode.recallingGroupInvitationIsDisabled.rawValue else { + throw error + } } - }) - assertCompleted("deleteJoinRequest_shouldSucceedOrThrowDisabledFunction", service.deleteJoinRequest(groupJoinRequestId!).recover { error -> Promise> in - if let businessError = error as? ResponseError, businessError.code == ResponseStatusCode.recallingGroupJoinRequestIsDisabled.rawValue { - return Promise.value(Response.empty()) - } else { - throw error + } + try await assertCompleted("deleteJoinRequest_shouldSucceedOrThrowDisabledFunction") { + do { + let result = try await service.deleteJoinRequest(groupJoinRequestId!) + } catch { + guard let businessError = error as? ResponseError, businessError.code == ResponseStatusCode.recallingGroupJoinRequestIsDisabled.rawValue else { + throw error + } } - }) - assertCompleted("quitGroup_shouldSucceed", service.quitGroup(groupId: groupId!, quitAfterTransfer: false)) + } + try await assertCompleted("quitGroup_shouldSucceed", await service.quitGroup(groupId: groupId!, quitAfterTransfer: false)) var readyToDeleteGroupId: Int64! - wait(service.createGroup(name: "readyToDelete").done { - readyToDeleteGroupId = $0.data - }) - assertCompleted("deleteGroup_shouldSucceed", service.deleteGroup(readyToDeleteGroupId)) + try await wait { + let result = try await service.createGroup(name: "readyToDelete") + readyToDeleteGroupId = result.data + } + try await assertCompleted("deleteGroup_shouldSucceed", await service.deleteGroup(readyToDeleteGroupId)) // Lowest-priority Update - assertCompleted("transferOwnership_shouldSucceed", service.transferOwnership(groupId: groupId!, successorId: groupSuccessorId, quitAfterTransfer: true)) + try await assertCompleted("transferOwnership_shouldSucceed", await service.transferOwnership(groupId: groupId!, successorId: groupSuccessorId, quitAfterTransfer: true)) } } diff --git a/turms-client-swift/Tests/TurmsClientTests/Service/MessageServiceTests.swift b/turms-client-swift/Tests/TurmsClientTests/Service/MessageServiceTests.swift index 3d2927bdc1..eeac912d89 100755 --- a/turms-client-swift/Tests/TurmsClientTests/Service/MessageServiceTests.swift +++ b/turms-client-swift/Tests/TurmsClientTests/Service/MessageServiceTests.swift @@ -1,17 +1,16 @@ -import PromiseKit @testable import TurmsClient import XCTest class MessageServiceTests: XCTestCase { - static let SENDER_ID: Int64 = 1 - static let RECIPIENT_ID: Int64 = 2 - static let GROUP_MEMBER_ID: Int64 = 3 - static let TARGET_GROUP_ID: Int64 = 1 - var senderClient: TurmsClient! - var recipientClient: TurmsClient! - var groupMemberClient: TurmsClient! - - func test_e2e() { + private static let SENDER_ID: Int64 = 1 + private static let RECIPIENT_ID: Int64 = 2 + private static let GROUP_MEMBER_ID: Int64 = 3 + private static let TARGET_GROUP_ID: Int64 = 1 + private var senderClient: TurmsClient! + private var recipientClient: TurmsClient! + private var groupMemberClient: TurmsClient! + + func test_e2e() async throws { var privateMessageId: Int64? var groupMessageId: Int64? @@ -20,38 +19,42 @@ class MessageServiceTests: XCTestCase { senderClient = TurmsClient(Config.HOST, Config.PORT) recipientClient = TurmsClient(Config.HOST, Config.PORT) groupMemberClient = TurmsClient(Config.HOST, Config.PORT) - wait(senderClient.userService.login(userId: MessageServiceTests.SENDER_ID, password: "123")) - wait(recipientClient.userService.login(userId: MessageServiceTests.RECIPIENT_ID, password: "123")) - wait(groupMemberClient.userService.login(userId: MessageServiceTests.GROUP_MEMBER_ID, password: "123")) + try await wait(await self.senderClient.userService.login(userId: MessageServiceTests.SENDER_ID, password: "123")) + try await wait(await self.recipientClient.userService.login(userId: MessageServiceTests.RECIPIENT_ID, password: "123")) + try await wait(await self.groupMemberClient.userService.login(userId: MessageServiceTests.GROUP_MEMBER_ID, password: "123")) let service = senderClient.messageService! // Create - assertCompleted("sendPrivateMessage_shouldReturnMessageId", service.sendMessage(isGroupMessage: false, targetId: MessageServiceTests.RECIPIENT_ID, deliveryDate: Date(), text: "hello").done { - privateMessageId = $0.data - }) - assertCompleted("sendGroupMessage_shouldReturnMessageId", service.sendMessage(isGroupMessage: true, targetId: MessageServiceTests.TARGET_GROUP_ID, deliveryDate: Date(), text: "hello").done { - groupMessageId = $0.data - }) - assertCompleted("forwardPrivateMessage_shouldReturnForwardedMessageId", service.forwardMessage(messageId: privateMessageId!, isGroupMessage: false, targetId: MessageServiceTests.RECIPIENT_ID)) - assertCompleted("forwardGroupMessage_shouldReturnForwardedMessageId", service.forwardMessage(messageId: groupMessageId!, isGroupMessage: true, targetId: MessageServiceTests.TARGET_GROUP_ID)) + try await assertCompleted("sendPrivateMessage_shouldReturnMessageId") { + let result = try await service.sendMessage(isGroupMessage: false, targetId: MessageServiceTests.RECIPIENT_ID, deliveryDate: Date(), text: "hello") + privateMessageId = result.data + } + try await assertCompleted("sendGroupMessage_shouldReturnMessageId") { + let result = try await service.sendMessage(isGroupMessage: true, targetId: MessageServiceTests.TARGET_GROUP_ID, deliveryDate: Date(), text: "hello") + groupMessageId = result.data + } + try await assertCompleted("forwardPrivateMessage_shouldReturnForwardedMessageId", await service.forwardMessage(messageId: privateMessageId!, isGroupMessage: false, targetId: MessageServiceTests.RECIPIENT_ID)) + try await assertCompleted("forwardGroupMessage_shouldReturnForwardedMessageId", await service.forwardMessage(messageId: groupMessageId!, isGroupMessage: true, targetId: MessageServiceTests.TARGET_GROUP_ID)) // Update - assertCompleted("recallMessage_shouldSucceed", service.recallMessage(messageId: groupMessageId!)) - assertCompleted("updateSentMessage_shouldSucceed", service.updateSentMessage(messageId: privateMessageId!, text: "I have modified the message")) + try await assertCompleted("recallMessage_shouldSucceed", await service.recallMessage(messageId: groupMessageId!)) + try await assertCompleted("updateSentMessage_shouldSucceed", await service.updateSentMessage(messageId: privateMessageId!, text: "I have modified the message")) // Query - assertCompleted("queryMessages_shouldReturnNotEmptyMessages", recipientClient.messageService.queryMessages(areGroupMessages: false, fromIds: [MessageServiceTests.SENDER_ID], maxCount: 10).done { - XCTAssertFalse($0.data.isEmpty) - }) - assertCompleted("queryMessagesWithTotal_shouldReturnNotEmptyMessagesWithTotal", service.queryMessagesWithTotal(areGroupMessages: false, fromIds: [MessageServiceTests.SENDER_ID], maxCount: 1).done { - XCTAssertFalse($0.data.isEmpty) - }) + try await assertCompleted("queryMessages_shouldReturnNotEmptyMessages") { + let messages = try await self.recipientClient.messageService.queryMessages(areGroupMessages: false, fromIds: [MessageServiceTests.SENDER_ID], maxCount: 10) + XCTAssertFalse(messages.data.isEmpty) + } + try await assertCompleted("queryMessagesWithTotal_shouldReturnNotEmptyMessagesWithTotal") { + let messages = try await service.queryMessagesWithTotal(areGroupMessages: false, fromIds: [MessageServiceTests.SENDER_ID], maxCount: 1) + XCTAssertFalse(messages.data.isEmpty) + } // Tear down - wait(senderClient.driver.disconnect()) - wait(recipientClient.driver.disconnect()) - wait(groupMemberClient.driver.disconnect()) + try await wait(await self.senderClient.driver.disconnect()) + try await wait(await self.recipientClient.driver.disconnect()) + try await wait(await self.groupMemberClient.driver.disconnect()) } // Util diff --git a/turms-client-swift/Tests/TurmsClientTests/Service/StorageServiceTests.swift b/turms-client-swift/Tests/TurmsClientTests/Service/StorageServiceTests.swift index 0d5f1070da..c4b1b82ef3 100644 --- a/turms-client-swift/Tests/TurmsClientTests/Service/StorageServiceTests.swift +++ b/turms-client-swift/Tests/TurmsClientTests/Service/StorageServiceTests.swift @@ -1,48 +1,51 @@ -import PromiseKit @testable import TurmsClient import XCTest class StorageServiceTests: XCTestCase { - var turmsClient: TurmsClient! - static let USER_ID: Int64 = 1 - static let GROUP_ID: Int64 = 1 - static let MEDIA_TYPE = "image/png" - static let PROFILE_PICTURE = Data([0, 1, 2, 3, 4]) - static let ATTACHMENT: Data = .init([0, 1, 2, 3, 4]) + private var turmsClient: TurmsClient! + private static let USER_ID: Int64 = 1 + private static let GROUP_ID: Int64 = 1 + private static let MEDIA_TYPE = "image/png" + private static let PROFILE_PICTURE = Data([0, 1, 2, 3, 4]) + private static let ATTACHMENT: Data = .init([0, 1, 2, 3, 4]) - override func setUp() { + override func setUp() async throws { continueAfterFailure = false turmsClient = TurmsClient(Config.HOST, Config.PORT, storageServerUrl: Config.STORAGE_SERVER_URL) - wait(turmsClient.userService.login(userId: StorageServiceTests.USER_ID, password: "123")) + try await wait(await self.turmsClient.userService.login(userId: StorageServiceTests.USER_ID, password: "123")) } - override func tearDown() { - wait(turmsClient.userService.logout()) + override func tearDown() async throws { + try await wait(await self.turmsClient.userService.logout()) } - func test_e2e() { + func test_e2e() async throws { var attachmentId: Int64! // Create - assertCompleted("uploadUserProfilePicture_shouldReturnUploadResult", turmsClient.storageService.uploadUserProfilePicture(data: StorageServiceTests.PROFILE_PICTURE, mediaType: StorageServiceTests.MEDIA_TYPE)) - assertCompleted("uploadGroupProfilePicture_shouldReturnUploadResult", turmsClient.storageService.uploadGroupProfilePicture(groupId: StorageServiceTests.GROUP_ID, data: StorageServiceTests.PROFILE_PICTURE, mediaType: StorageServiceTests.MEDIA_TYPE)) + try await assertCompleted("uploadUserProfilePicture_shouldReturnUploadResult", await self.turmsClient.storageService.uploadUserProfilePicture(data: StorageServiceTests.PROFILE_PICTURE, mediaType: StorageServiceTests.MEDIA_TYPE)) + try await assertCompleted("uploadGroupProfilePicture_shouldReturnUploadResult", await self.turmsClient.storageService.uploadGroupProfilePicture(groupId: StorageServiceTests.GROUP_ID, data: StorageServiceTests.PROFILE_PICTURE, mediaType: StorageServiceTests.MEDIA_TYPE)) - assertCompleted("uploadMessageAttachment_shouldReturnUploadResult", turmsClient.storageService.uploadMessageAttachment(data: StorageServiceTests.ATTACHMENT, mediaType: StorageServiceTests.MEDIA_TYPE).done { - attachmentId = $0.data.resourceIdNum! - }) + try await assertCompleted("uploadMessageAttachment_shouldReturnUploadResult") { + let result = try await self.turmsClient.storageService.uploadMessageAttachment(data: StorageServiceTests.ATTACHMENT, mediaType: StorageServiceTests.MEDIA_TYPE) + attachmentId = result.data.resourceIdNum! + } // Query - assertCompleted("queryUserProfilePicture_shouldEqualUploadedPicture", turmsClient.storageService.queryUserProfilePicture(userId: StorageServiceTests.USER_ID).done { - XCTAssertEqual(StorageServiceTests.PROFILE_PICTURE, $0.data.data) - }) - assertCompleted("queryGroupProfilePicture_shouldEqualUploadedPicture", turmsClient.storageService.queryGroupProfilePicture(groupId: StorageServiceTests.GROUP_ID).done { - XCTAssertEqual(StorageServiceTests.PROFILE_PICTURE, $0.data.data) - }) - assertCompleted("queryMessageAttachment_shouldEqualUploadedAttachment", turmsClient.storageService.queryMessageAttachment(attachmentIdNum: attachmentId).done { - XCTAssertEqual(StorageServiceTests.ATTACHMENT, $0.data.data) - }) + try await assertCompleted("queryUserProfilePicture_shouldEqualUploadedPicture") { + let result = try await self.turmsClient.storageService.queryUserProfilePicture(userId: StorageServiceTests.USER_ID) + XCTAssertEqual(StorageServiceTests.PROFILE_PICTURE, result.data.data) + } + try await assertCompleted("queryGroupProfilePicture_shouldEqualUploadedPicture") { + let result = try await self.turmsClient.storageService.queryGroupProfilePicture(groupId: StorageServiceTests.GROUP_ID) + XCTAssertEqual(StorageServiceTests.PROFILE_PICTURE, result.data.data) + } + try await assertCompleted("queryMessageAttachment_shouldEqualUploadedAttachment") { + let result = try await self.turmsClient.storageService.queryMessageAttachment(attachmentIdNum: attachmentId) + XCTAssertEqual(StorageServiceTests.ATTACHMENT, result.data.data) + } // Delete - assertCompleted("deleteUserProfilePicture_shouldSucceed", turmsClient.storageService.deleteUserProfilePicture()) - assertCompleted("deleteGroupProfilePicture_shouldSucceed", turmsClient.storageService.deleteGroupProfilePicture(groupId: StorageServiceTests.GROUP_ID)) + try await assertCompleted("deleteUserProfilePicture_shouldSucceed", await self.turmsClient.storageService.deleteUserProfilePicture()) + try await assertCompleted("deleteGroupProfilePicture_shouldSucceed", await self.turmsClient.storageService.deleteGroupProfilePicture(groupId: StorageServiceTests.GROUP_ID)) } } diff --git a/turms-client-swift/Tests/TurmsClientTests/Service/UserServiceTests.swift b/turms-client-swift/Tests/TurmsClientTests/Service/UserServiceTests.swift index ad0c0da6a9..ec1e2968d0 100755 --- a/turms-client-swift/Tests/TurmsClientTests/Service/UserServiceTests.swift +++ b/turms-client-swift/Tests/TurmsClientTests/Service/UserServiceTests.swift @@ -1,90 +1,99 @@ -import PromiseKit @testable import TurmsClient import XCTest class UserServiceTests: XCTestCase { - var turmsClient: TurmsClient! + private var turmsClient: TurmsClient! override func setUp() { continueAfterFailure = false turmsClient = TurmsClient(Config.HOST, Config.PORT) } - override func tearDown() { - wait(turmsClient.userService.logout()) + override func tearDown() async throws { + try await wait(await self.turmsClient.userService.logout()) } - func test_e2e() { + func test_e2e() async throws { var relationshipGroupIndex: Int32! let userStatus = UserStatus.busy let service = turmsClient.userService! // Login - assertCompleted("login_shouldSucceed", service.login(userId: 1, password: "123")) + try await assertCompleted("login_shouldSucceed", await service.login(userId: 1, password: "123")) // Create - assertCompleted("createRelationship_shouldSucceed", service.createRelationship(userId: 10, isBlocked: true).recover { error -> Promise> in - if let businessError = error as? ResponseError, businessError.code == ResponseStatusCode.createExistingRelationship.rawValue { - return Promise.value(Response.empty()) - } else { - throw error + try await assertCompleted("createRelationship_shouldSucceed") { + do { + let result = try await service.createRelationship(userId: 10, isBlocked: true) + } catch { + guard let businessError = error as? ResponseError, businessError.code == ResponseStatusCode.createExistingRelationship.rawValue else { + throw error + } } - }) - assertCompleted("createFriendRelationship_shouldSucceed", service.createFriendRelationship(userId: 10).recover { error -> Promise> in - if let businessError = error as? ResponseError, businessError.code == ResponseStatusCode.createExistingRelationship.rawValue { - return Promise.value(Response.empty()) - } else { - throw error + } + try await assertCompleted("createFriendRelationship_shouldSucceed") { + do { + let result = try await service.createFriendRelationship(userId: 10) + } catch { + guard let businessError = error as? ResponseError, businessError.code == ResponseStatusCode.createExistingRelationship.rawValue else { + throw error + } } - }) - assertCompleted("createBlockedUserRelationship_shouldSucceed", service.createBlockedUserRelationship(userId: 10).recover { error -> Promise> in - if let businessError = error as? ResponseError, businessError.code == ResponseStatusCode.createExistingRelationship.rawValue { - return Promise.value(Response.empty()) - } else { - throw error + } + try await assertCompleted("createBlockedUserRelationship_shouldSucceed") { + do { + let result = try await service.createBlockedUserRelationship(userId: 10) + } catch { + guard let businessError = error as? ResponseError, businessError.code == ResponseStatusCode.createExistingRelationship.rawValue else { + throw error + } } - }) - assertCompleted("sendFriendRequest_shouldReturnFriendRequestId", service.sendFriendRequest(recipientId: 11, content: "content").recover { error -> Promise> in - if let businessError = error as? ResponseError, businessError.code == ResponseStatusCode.createExistingFriendRequest.rawValue { - return Promise.value(Response.value(0)) - } else { - throw error + } + try await assertCompleted("sendFriendRequest_shouldReturnFriendRequestId") { + do { + let result = try await service.sendFriendRequest(recipientId: 11, content: "content") + } catch { + guard let businessError = error as? ResponseError, businessError.code == ResponseStatusCode.createExistingFriendRequest.rawValue else { + throw error + } } - }) - assertCompleted("createRelationshipGroup_shouldReturnRelationshipGroupIndex", service.createRelationshipGroup("newGroup").done { - relationshipGroupIndex = $0.data - }) + } + try await assertCompleted("createRelationshipGroup_shouldReturnRelationshipGroupIndex") { + let result = try await service.createRelationshipGroup("newGroup") + relationshipGroupIndex = result.data + } // Update - assertCompleted("updateUserOnlineStatus_shouldSucceed", service.updateUserOnlineStatus(userStatus)) - assertCompleted("updatePassword_shouldSucceed", service.updatePassword("123")) - assertCompleted("updateProfile_shouldSucceed", service.updateProfile(name: "123", intro: "123")) - assertCompleted("updateRelationship_shouldSucceed", service.updateRelationship(relatedUserId: 10, groupIndex: 1)) - assertCompleted("replyFriendRequest_shouldSucceed", service.replyFriendRequest(requestId: 10, responseAction: .accept, reason: "reason")) - assertCompleted("updateRelationshipGroup_shouldSucceed", service.updateRelationshipGroup(groupIndex: relationshipGroupIndex, newName: "newGroupName")) - assertCompleted("moveRelatedUserToGroup_shouldSucceed", service.moveRelatedUserToGroup(relatedUserId: 2, groupIndex: 1)) - wait(service.moveRelatedUserToGroup(relatedUserId: 2, groupIndex: 0)) - assertCompleted("updateLocation_shouldSucceed", service.updateLocation(latitude: 2, longitude: 2)) + try await assertCompleted("updateUserOnlineStatus_shouldSucceed", await service.updateUserOnlineStatus(userStatus)) + try await assertCompleted("updatePassword_shouldSucceed", await service.updatePassword("123")) + try await assertCompleted("updateProfile_shouldSucceed", await service.updateProfile(name: "123", intro: "123")) + try await assertCompleted("updateRelationship_shouldSucceed", await service.updateRelationship(relatedUserId: 10, groupIndex: 1)) + try await assertCompleted("replyFriendRequest_shouldSucceed", await service.replyFriendRequest(requestId: 10, responseAction: .accept, reason: "reason")) + try await assertCompleted("updateRelationshipGroup_shouldSucceed", await service.updateRelationshipGroup(groupIndex: relationshipGroupIndex, newName: "newGroupName")) + try await assertCompleted("moveRelatedUserToGroup_shouldSucceed", await service.moveRelatedUserToGroup(relatedUserId: 2, groupIndex: 1)) + try await wait(await service.moveRelatedUserToGroup(relatedUserId: 2, groupIndex: 0)) + try await assertCompleted("updateLocation_shouldSucceed", await service.updateLocation(latitude: 2, longitude: 2)) // Query - assertCompleted("queryUserProfiles_shouldReturnUserInfos", service.queryUserProfiles(userIds: [1])) - assertCompleted("queryNearbyUsers_shouldReturnNearbyUsers", service.queryNearbyUsers(latitude: 1, longitude: 1)) - assertCompleted("queryUserOnlineStatusesRequest_shouldUserOnlineStatuses", service.queryUserOnlineStatusesRequest([1]).done { - XCTAssertEqual($0.data[0].userStatus, userStatus) - }) - assertCompleted("queryRelationships_shouldReturnUserRelationshipsWithVersion", service.queryRelationships(relatedUserIds: [2])) - assertCompleted("queryRelatedUserIds_shouldReturnRelatedUserIds", service.queryRelatedUserIds()) - assertCompleted("queryFriends_shouldReturnFriendRelationships", service.queryFriends()) - assertCompleted("queryBlockedUsers_shouldReturnRelationshipsWithBlockedUsers", service.queryBlockedUsers()) - assertCompleted("queryFriendRequests_shouldReturnFriendRequests", service.queryFriendRequests(true)) - assertCompleted("queryRelationshipGroups_shouldReturnRelationshipGroups", service.queryRelationshipGroups()) + try await assertCompleted("queryUserProfiles_shouldReturnUserInfos", await service.queryUserProfiles(userIds: [1])) + try await assertCompleted("queryNearbyUsers_shouldReturnNearbyUsers", await service.queryNearbyUsers(latitude: 1, longitude: 1)) + try await assertCompleted("queryUserOnlineStatusesRequest_shouldUserOnlineStatuses") { + let result = try await service.queryUserOnlineStatusesRequest([1]) + XCTAssertEqual(result.data[0].userStatus, userStatus) + } + try await assertCompleted("queryRelationships_shouldReturnUserRelationshipsWithVersion", await service.queryRelationships(relatedUserIds: [2])) + try await assertCompleted("queryRelatedUserIds_shouldReturnRelatedUserIds", await service.queryRelatedUserIds()) + try await assertCompleted("queryFriends_shouldReturnFriendRelationships", await service.queryFriends()) + try await assertCompleted("queryBlockedUsers_shouldReturnRelationshipsWithBlockedUsers", await service.queryBlockedUsers()) + try await assertCompleted("queryFriendRequests_shouldReturnFriendRequests", await service.queryFriendRequests(true)) + try await assertCompleted("queryRelationshipGroups_shouldReturnRelationshipGroups", await service.queryRelationshipGroups()) // Delete - assertCompleted("deleteRelationship_shouldSucceed", service.deleteRelationship(relatedUserId: 10)) - assertCompleted("deleteRelationshipGroups_shouldSucceed", service.deleteRelationshipGroups(groupIndex: relationshipGroupIndex)) + try await assertCompleted("deleteRelationship_shouldSucceed", await service.deleteRelationship(relatedUserId: 10)) + try await assertCompleted("deleteRelationshipGroups_shouldSucceed", await service.deleteRelationshipGroups(groupIndex: relationshipGroupIndex)) // Logout - wait(service.logout()) + try await wait(await service.logout()) } }