diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b6af721..87f8b390 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ The changelog for [Kommunicate-iOS-SDK](https://github.com/Kommunicate-io/Kommunicate-iOS-SDK). Also see the [releases](https://github.com/Kommunicate-io/Kommunicate-iOS-SDK/releases) on Github. +## [7.2.5] 2024-12-24 +- Fixed typo of registerUserAsVistor with registerUserAsVisitor. +- SSL pinning enable disable configuration added. +- Fixed the issue where video thumbnails were not displaying correctly. + ## [7.2.4] 2024-12-03 - Fixed Away Message not showing in Bot Delay. - Fixed an issue where captions were cut off in Video Rich Messages. diff --git a/Example/Kommunicate/LoginViewController.swift b/Example/Kommunicate/LoginViewController.swift index 40cc9867..001c4151 100644 --- a/Example/Kommunicate/LoginViewController.swift +++ b/Example/Kommunicate/LoginViewController.swift @@ -95,7 +95,7 @@ setupApplicationKey(applicationId) let kmUser = userWithUserId(Kommunicate.randomId(), andApplicationId: applicationId) activityIndicator.startAnimating() - Kommunicate.registerUserAsVistor(kmUser, completion: { + Kommunicate.registerUserAsVisitor(kmUser, completion: { response, error in self.activityIndicator.stopAnimating() guard error == nil else { diff --git a/Example/Podfile.lock b/Example/Podfile.lock index fe52c0a3..ab6ce625 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -15,19 +15,19 @@ PODS: - iOSSnapshotTestCase/SwiftSupport (8.0.0): - iOSSnapshotTestCase/Core - Kingfisher (7.6.2) - - Kommunicate (7.2.4): - - KommunicateChatUI-iOS-SDK (= 1.3.8) - - KommunicateChatUI-iOS-SDK (1.3.8): - - KommunicateChatUI-iOS-SDK/Complete (= 1.3.8) - - KommunicateChatUI-iOS-SDK/Complete (1.3.8): + - Kommunicate (7.2.5): + - KommunicateChatUI-iOS-SDK (= 1.3.9) + - KommunicateChatUI-iOS-SDK (1.3.9): + - KommunicateChatUI-iOS-SDK/Complete (= 1.3.9) + - KommunicateChatUI-iOS-SDK/Complete (1.3.9): - iOSDropDown - Kingfisher (~> 7.6.2) - KommunicateChatUI-iOS-SDK/RichMessageKit - - KommunicateCore-iOS-SDK (= 1.2.4) + - KommunicateCore-iOS-SDK (= 1.2.5) - SwipeCellKit (~> 2.7.1) - - KommunicateChatUI-iOS-SDK/RichMessageKit (1.3.8) - - KommunicateCore-iOS-SDK (1.2.4) - - Nimble (13.6.2): + - KommunicateChatUI-iOS-SDK/RichMessageKit (1.3.9) + - KommunicateCore-iOS-SDK (1.2.5) + - Nimble (13.7.1): - CwlPreconditionTesting (~> 2.2.0) - Nimble-Snapshots (9.8.0): - Nimble-Snapshots/Core (= 9.8.0) @@ -74,10 +74,10 @@ SPEC CHECKSUMS: iOSDropDown: ce9daa584eaa5567cafc1b633e3cc7eb6d9cea42 iOSSnapshotTestCase: a670511f9ee3829c2b9c23e6e68f315fd7b6790f Kingfisher: 6c5449c6450c5239166510ba04afe374a98afc4f - Kommunicate: 3d18f7229fbf1ff7e16a027cbc5e3d6fe3316f30 - KommunicateChatUI-iOS-SDK: 4ca12728c40d134b330f1e8ac5335e1b98ca6552 - KommunicateCore-iOS-SDK: 2adcd775f5ed6fdcdbcac47986144c177fc44ab0 - Nimble: 8cd9e713948ea6a61980e82d2bec937acec16b91 + Kommunicate: c886c6b4cdd32d4976eab22968ab1f05e0a9f08e + KommunicateChatUI-iOS-SDK: 53efa89eaab6e8af7a7dbd9f34e106ac6171c279 + KommunicateCore-iOS-SDK: 5dc0d852f3e49c37b33a808d73556cf683b2f6c0 + Nimble: 317d713c30c3336dd8571da1889f7ec3afc626e8 Nimble-Snapshots: 240ea76ee8e52dfc1387b8d0d9bf1db3420cabbd Quick: b8bec97cd4b9f21da0472d45580f763b801fc353 SwipeCellKit: 3972254a826da74609926daf59b08d6c72e619ea diff --git a/Example/Tests/KommunicateTests.swift b/Example/Tests/KommunicateTests.swift index fcee2fa2..b79a70e1 100644 --- a/Example/Tests/KommunicateTests.swift +++ b/Example/Tests/KommunicateTests.swift @@ -8,6 +8,7 @@ class KommunicateTests: XCTestCase { static var showConversationsCalled = false static var createConversationsCalled = false static var loggedIn = true + static var conversationID: String? override class var isLoggedIn: Bool { return loggedIn @@ -130,7 +131,206 @@ class KommunicateTests: XCTestCase { expectation.fulfill() } } + + func testCreateAndLaunchConversationWithCustomData() { + KommunicateMock.applozicClientType = ApplozicClientMock.self + let expectation = self.expectation(description: "Completion handler called") + + let kmConversation = KMConversationBuilder() + .useLastConversation(false) + .setPreFilledMessage("This is a sample prefilled message.") + .withMetaData(["TestMetadata": "SampleValue"]) + .withConversationTitle("Automation Conversation") + .build() + + if KommunicateMock.isLoggedIn { + launchConversation(kmConversation, expectation: expectation) + } else { + KommunicateMock.registerUserAsVistor { response, error in + if let error = error { + XCTFail("User registration failed: \(error.localizedDescription)") + expectation.fulfill() + return + } + KommunicateMock.loggedIn = true + self.launchConversation(kmConversation, expectation: expectation) + } + } + waitForExpectations(timeout: 30) + } + + private var testWindow: UIWindow? // Property to retain UIWindow during the test lifecycle + + private func launchConversation(_ conversation: KMConversation, expectation: XCTestExpectation) { + // Retain the UIWindow to prevent deallocation + let dummyViewController = UIViewController() + testWindow = UIWindow(frame: UIScreen.main.bounds) + testWindow?.rootViewController = dummyViewController + testWindow?.makeKeyAndVisible() + + // Launch the conversation + KommunicateMock.launchConversation(conversation: conversation, viewController: dummyViewController) { result in + switch result { + case .success(let conversationId): + print("Conversation id:", conversationId) + XCTAssertTrue(true, "Conversation created successfully.") + case .failure(let kmConversationError): + XCTAssertNotNil(kmConversationError, "Conversation creation failed") + XCTFail("Failed to create conversation.") + } + expectation.fulfill() + } + } + + func testUpdateConversationAssignee() { + KommunicateMock.applozicClientType = ApplozicClientMock.self + let expectation = self.expectation(description: "Completion handler called") + let assigneeId = "alex-nwqih" + + // Helper function to handle conversation update + func updateConversation(with conversationId: String) { + let conversation = KMConversationBuilder() + .withClientConversationId(conversationId) + .withConversationAssignee(assigneeId) + .build() + + KommunicateMock.updateConversation(conversation: conversation) { response in + switch response { + case .success: + XCTAssertTrue(true, "Conversation is updated successfully") + case .failure: + XCTFail("Failed to update conversation") + } + expectation.fulfill() + } + } + + if let conversationId = KommunicateMock.conversationID { + // If conversationID exists, update conversation + updateConversation(with: conversationId) + } else { + // Otherwise, create a new conversation and update + let kmConversation = KMConversationBuilder() + .useLastConversation(false) + .withMetaData(["TestMetadata": "SampleValue"]) + .withConversationTitle("Automation Conversation") + .build() + + KommunicateMock.createConversation(conversation: kmConversation) { [weak self] result in + switch result { + case .success(let conversationId): + KommunicateMock.conversationID = conversationId + KommunicateMock.loggedIn = true + updateConversation(with: conversationId) + case .failure(let error): + XCTAssertNotNil(error, "Conversation creation failed") + expectation.fulfill() + } + } + } + + waitForExpectations(timeout: 30) + } + + func testUpdateTeamID() { + KommunicateMock.applozicClientType = ApplozicClientMock.self + let expectation = self.expectation(description: "Completion handler called") + let teamID = "107732724" + + // Helper function to handle conversation update + func updateConversation(with conversationId: String) { + let conversation = KMConversationBuilder() + .withClientConversationId(conversationId) + .withTeamId(teamID) + .build() + + KommunicateMock.updateConversation(conversation: conversation) { response in + switch response { + case .success: + XCTAssertTrue(true, "Conversation is updated successfully") + case .failure: + XCTFail("Failed to update conversation") + } + expectation.fulfill() + } + } + + if let conversationId = KommunicateMock.conversationID { + // If conversationID exists, update conversation + updateConversation(with: conversationId) + } else { + // Otherwise, create a new conversation and update + let kmConversation = KMConversationBuilder() + .useLastConversation(false) + .withMetaData(["TestMetadata": "SampleValue"]) + .withConversationTitle("Automation Conversation") + .build() + + KommunicateMock.createConversation(conversation: kmConversation) { [weak self] result in + switch result { + case .success(let conversationId): + KommunicateMock.conversationID = conversationId + KommunicateMock.loggedIn = true + updateConversation(with: conversationId) + case .failure(let error): + XCTAssertNotNil(error, "Conversation creation failed") + expectation.fulfill() + } + } + } + + waitForExpectations(timeout: 30) + } + + func testUpdateConversationMetadata() { + KommunicateMock.applozicClientType = ApplozicClientMock.self + let expectation = self.expectation(description: "Completion handler called") + let metaData = ["name": "Alice", "city": "London", "hobby": "Painting"] + + // Helper function to handle conversation update + func updateConversation(with conversationId: String) { + let conversation = KMConversationBuilder() + .withClientConversationId(conversationId) + .withMetaData(metaData) + .build() + + KommunicateMock.updateConversation(conversation: conversation) { response in + switch response { + case .success: + XCTAssertTrue(true, "Conversation is updated successfully") + case .failure: + XCTFail("Failed to update conversation") + } + expectation.fulfill() + } + } + if let conversationId = KommunicateMock.conversationID { + // If conversationID exists, update conversation + updateConversation(with: conversationId) + } else { + // Otherwise, create a new conversation and update + let kmConversation = KMConversationBuilder() + .useLastConversation(false) + .withMetaData(["TestMetadata": "SampleValue"]) + .withConversationTitle("Automation Conversation") + .build() + + KommunicateMock.createConversation(conversation: kmConversation) { [weak self] result in + switch result { + case .success(let conversationId): + KommunicateMock.conversationID = conversationId + updateConversation(with: conversationId) + KommunicateMock.loggedIn = true + case .failure(let error): + XCTAssertNotNil(error, "Conversation creation failed") + expectation.fulfill() + } + } + } + + waitForExpectations(timeout: 30) + } func testSendMessageFunction() { KommunicateMock.applozicClientType = ApplozicClientMock.self diff --git a/Kommunicate.podspec b/Kommunicate.podspec index 23fc174d..1c0e94dd 100644 --- a/Kommunicate.podspec +++ b/Kommunicate.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Kommunicate' - s.version = '7.2.4' + s.version = '7.2.5' s.summary = 'Kommunicate iOS SDK for customer support.' s.homepage = 'https://github.com/Kommunicate-io/Kommunicate-iOS-SDK' s.license = { :type => 'BSD-3-Clause', :file => 'LICENSE' } @@ -10,5 +10,5 @@ Pod::Spec.new do |s| s.swift_version = '5.0' s.source_files = 'Sources/Kommunicate/Classes/**/*.{swift}' s.resources = 'Sources/Resources/**/*{lproj,storyboard,xib,xcassets,json,strings}' - s.dependency 'KommunicateChatUI-iOS-SDK' , '1.3.8' + s.dependency 'KommunicateChatUI-iOS-SDK' , '1.3.9' end \ No newline at end of file diff --git a/Package.resolved b/Package.resolved index 91ff6cfd..adbca147 100644 --- a/Package.resolved +++ b/Package.resolved @@ -33,8 +33,8 @@ "repositoryURL": "https://github.com/Kommunicate-io/KommunicateChatUI-iOS-SDK.git", "state": { "branch": null, - "revision": "4c03ffe31c5f08f3ccb5cf5500b4fa009b4d491a", - "version": "1.3.8" + "revision": "1de7210aa58867aeb63b8267d0bd0f3c1f1c1c47", + "version": "1.3.9" } }, { @@ -42,8 +42,8 @@ "repositoryURL": "https://github.com/Kommunicate-io/KommunicateCore-iOS-SDK.git", "state": { "branch": null, - "revision": "61118a46f66d9a778294bbd97086203ac3fed53b", - "version": "1.2.4" + "revision": "f1863e9abc4321b483be128965123e1603e50d8b", + "version": "1.2.5" } }, { diff --git a/Package.swift b/Package.swift index 2563a094..c6f24d8e 100644 --- a/Package.swift +++ b/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(name: "KommunicateChatUI-iOS-SDK", url: "https://github.com/Kommunicate-io/KommunicateChatUI-iOS-SDK.git", from: "1.3.8"), + .package(name: "KommunicateChatUI-iOS-SDK", url: "https://github.com/Kommunicate-io/KommunicateChatUI-iOS-SDK.git", from: "1.3.9"), ], targets: [ .target( diff --git a/Sources/Kommunicate/Classes/DataLoader.swift b/Sources/Kommunicate/Classes/DataLoader.swift index cce6a261..84fcb5e5 100644 --- a/Sources/Kommunicate/Classes/DataLoader.swift +++ b/Sources/Kommunicate/Classes/DataLoader.swift @@ -23,11 +23,13 @@ class DataLoader { // set up the session let config = URLSessionConfiguration.default - let session = URLSession( - configuration: config, - delegate: KMURLSessionPinningDelegate(), - delegateQueue: nil - ) + let session: URLSession = { + if Kommunicate.isKMSSLPinningEnabled { + return URLSession(configuration: config, delegate: KMURLSessionPinningDelegate(), delegateQueue: nil) + } else { + return URLSession(configuration: config) + } + }() // make the request let task = session.dataTask(with: urlRequest) { diff --git a/Sources/Kommunicate/Classes/KMConversation.swift b/Sources/Kommunicate/Classes/KMConversation.swift index 554bf8cf..cf791664 100644 --- a/Sources/Kommunicate/Classes/KMConversation.swift +++ b/Sources/Kommunicate/Classes/KMConversation.swift @@ -23,6 +23,7 @@ import KommunicateCore_iOS_SDK public var teamId: String? public var defaultConversationAssignee: String? public var appName: String? = Bundle.main.displayName + public var prefilledMessage: String? = nil public init(userId: String) { self.userId = userId @@ -120,6 +121,14 @@ import KommunicateCore_iOS_SDK conversation.teamId = teamId return self } + + /// To set pre fill message in Chat Bar while opening converwastion. + /// - Parameter message: Pass the text that should be vissble on chat bar while opening the conversation. Only works with `launchConversation` + @discardableResult + @objc public func setPreFilledMessage(_ messsage: String) -> KMConversationBuilder { + conversation.prefilledMessage = messsage + return self + } /// Finally call the build method on the KMConversationBuilder to build the KMConversation @objc public func build() -> KMConversation { diff --git a/Sources/Kommunicate/Classes/Kommunicate.swift b/Sources/Kommunicate/Classes/Kommunicate.swift index ca3b74bd..521bbc42 100644 --- a/Sources/Kommunicate/Classes/Kommunicate.swift +++ b/Sources/Kommunicate/Classes/Kommunicate.swift @@ -83,6 +83,8 @@ open class Kommunicate: NSObject, Localizable { config.chatBar.optionsToShow = .some([.camera, .location, .gallery, .video, .document]) return config }() + + public static var isKMSSLPinningEnabled: Bool = false /// Configuration which defines the behavior of ConversationView components. public static var kmConversationViewConfiguration = KMConversationViewConfiguration() @@ -213,6 +215,7 @@ open class Kommunicate: NSObject, Localizable { assertionFailure("Kommunicate App ID: Empty value passed") return } + ALUserDefaultsHandler.setKMSSLPinningEnabled(isKMSSLPinningEnabled) guard KMUserDefaultHandler.isAppIdEmpty || KMUserDefaultHandler.matchesCurrentAppId(applicationId) else { @@ -266,7 +269,16 @@ open class Kommunicate: NSObject, Localizable { registerNewUser(kmUser, isVisitor: false, completion: completion) } - @objc open class func registerUserAsVistor( + /// Deprecated wrapper for backward compatibility + @available(*, deprecated, message: "Use `registerUserAsVisitor(_:completion:)` instead.") + @objc open class func registerUserAsVistor( // Note: Typo preserved intentionally + _ kmUser: KMUser = createVisitorUser(), + completion: @escaping (_ response: ALRegistrationResponse?, _ error: NSError?) -> Void + ) { + registerUserAsVisitor(kmUser, completion: completion) + } + + @objc open class func registerUserAsVisitor( _ kmUser: KMUser = createVisitorUser(), completion: @escaping (_ response: ALRegistrationResponse?, _ error: NSError?) -> Void ) { @@ -492,6 +504,98 @@ open class Kommunicate: NSObject, Localizable { } } + /** + Launch a new conversation with the details passed in group chat from a ViewController + + - Parameters: + - conversation: An instance of `KMConversation` object. + - viewController: ViewController from which the group chat will be launched. + - completionHandler: If successful launch the conversation the success callback will have a conversationId else it will be KMConversationError on failure. + + */ + open class func launchConversation( + conversation: KMConversation, + viewController: UIViewController, + completion: @escaping (Result) -> Void + ) { + // Check if the device is jailbroken + guard !isDeviceJailbroken() else { + print("The user device is suspected to be rooted.") + completion(.failure(KMConversationError.deviceRooted)) + return + } + + // Check network availability + guard ALDataNetworkConnection.checkDataNetworkAvailable() else { + completion(.failure(KMConversationError.internet)) + return + } + + // Validate conversation title + if let conversationTitle = conversation.conversationTitle, + conversationTitle.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + print("The conversation title should not be empty") + completion(.failure(KMConversationError.invalidTitle)) + return + } + + let service = KMConversationService() + + // Check user login status + guard KMUserDefaultHandler.isLoggedIn() else { + completion(.failure(KMConversationError.notLoggedIn)) + return + } + + // Refresh app settings + refreshAppsettings() + + // Determine if single-threaded conversation is enabled + let isSingleThreaded = ALApplozicSettings.getIsSingleThreadedEnabled() + if isSingleThreaded { + conversation.useLastConversation = isSingleThreaded + } + + // Handle clientConversationId for single-threaded conversations + if (conversation.clientConversationId ?? "").isEmpty, conversation.useLastConversation { + conversation.clientConversationId = service.createClientIdFrom( + userId: conversation.userId, + agentIds: conversation.agentIds, + botIds: conversation.botIds ?? [] + ) + } + + // Create a new conversation + service.createConversation(conversation: conversation) { response in + DispatchQueue.main.async { + guard let conversationId = response.clientChannelKey else { + completion(.failure(KMConversationError.api(response.error))) + return + } + + // Publish a custom event for the new conversation + KMCustomEventHandler.shared.publish( + triggeredEvent: KMCustomEvent.newConversation, + data: ["conversationId": conversationId] + ) + + // Show the conversation view + showConversationWith( + groupId: conversationId, + from: viewController, + prefilledMessage: conversation.prefilledMessage + ) { success in + DispatchQueue.main.async { + guard success else { + completion(.failure(KMConversationError.api(KommunicateError.conversationOpenFailed))) + return + } + completion(.success(conversationId)) + } + } + } + } + } /// This method is used to return an instance of conversation list view controller. ///