From 3f23e4d2348567605f09962ff0e0384dde2f3ba7 Mon Sep 17 00:00:00 2001 From: Abhijeet Ranjan Date: Wed, 30 Jul 2025 17:09:55 +0530 Subject: [PATCH 1/2] Add launchConversationWithUser API and event updates Introduces launchConversationWithUser method for both Android and iOS to support launching conversations with user details and options. Updates event names for consistency between platforms and adds a KMUser parsing helper on iOS. Also bumps KommunicateUI SDK version to 2.14.2 in Android. --- android/build.gradle | 2 +- .../io/kommunicate/app/KmEventListener.java | 21 +- .../app/RNKommunicateChatModule.java | 87 ++++++++ ios/RNKommunicateChat.swift | 196 +++++++++++++++++- 4 files changed, 290 insertions(+), 16 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index b86e1d1..962f472 100755 --- a/android/build.gradle +++ b/android/build.gradle @@ -40,5 +40,5 @@ repositories { dependencies { implementation 'com.facebook.react:react-native:+' - api 'io.kommunicate.sdk:kommunicateui:2.14.1' + api 'io.kommunicate.sdk:kommunicateui:2.14.2' } diff --git a/android/src/main/java/io/kommunicate/app/KmEventListener.java b/android/src/main/java/io/kommunicate/app/KmEventListener.java index 02b0ba6..e75da2f 100644 --- a/android/src/main/java/io/kommunicate/app/KmEventListener.java +++ b/android/src/main/java/io/kommunicate/app/KmEventListener.java @@ -23,7 +23,7 @@ public void register(ReactApplicationContext reactContext) { } public void unregister() { - EventManager.getInstance().unregisterPluginEventListener(); + EventManager.getInstance().unregisterPluginEventListener(); } private void sendEvent(String eventName, String value) { @@ -41,7 +41,7 @@ public void onPluginLaunch() { @Override public void onPluginDismiss() { - sendEvent("onPluginDismiss", "dismiss"); + sendEvent("onPluginDismiss", "dismiss"); } @Override @@ -108,12 +108,12 @@ public void onAttachmentClick(String attachmentType) { @Override public void onFaqClick(String FaqUrl) { - sendEvent("onFaqClick", FaqUrl); + sendEvent("onFaqClick", FaqUrl); } @Override public void onLocationClick() { - sendEvent("onLocationClick", "clicked"); + sendEvent("onLocationClick", "clicked"); } @Override @@ -123,17 +123,22 @@ public void onNotificationClick(Message message){ @Override public void onVoiceButtonClick(String action){ - sendEvent("onVoiceButtonClick", action); + sendEvent("onVoiceButtonClick", action); } @Override - public void onRatingEmoticonsClick(Integer ratingValue){ + public void onRatingEmoticonsClick(Integer ratingValue) { sendEvent("onRatingEmoticonsClick", String.valueOf(ratingValue)); } @Override - public void onRateConversationClick(){ - sendEvent("onRateConversationClick", "clicked"); + public void onRateConversationClick() { + sendEvent("onRateConversationClick", "clicked"); + } + + @Override + public void onCurrentOpenedConversation(Integer conversationId) { + sendEvent("onCurrentConversationOpened", String.valueOf(conversationId)); } } \ No newline at end of file diff --git a/android/src/main/java/io/kommunicate/app/RNKommunicateChatModule.java b/android/src/main/java/io/kommunicate/app/RNKommunicateChatModule.java index 131e9c4..f1c1015 100755 --- a/android/src/main/java/io/kommunicate/app/RNKommunicateChatModule.java +++ b/android/src/main/java/io/kommunicate/app/RNKommunicateChatModule.java @@ -59,6 +59,7 @@ public class RNKommunicateChatModule extends ReactContextBaseJavaModule { private static final String MESSAGE_METADATA = "messageMetadata"; private static final String TEAM_ID = "teamId"; private static final String CONVERSATION_INFO = "conversationInfo"; + private static final String APP_ID = "appId"; private static final String LANGUAGES = "languages"; private static final String KM_USER = "kmUser"; private KmEventListener kmEventListener; @@ -216,6 +217,92 @@ public void onFailure(RegistrationResponse registrationResponse, Exception excep }); } + @ReactMethod + public void launchConversationWithUser(final ReadableMap jsonObject, final Callback callback) { + final Activity currentActivity = getCurrentActivity(); + if (currentActivity == null) { + callback.invoke(ERROR, "Activity doesn't exist"); + return; + } + + try { + KMUser user = null; + Map conversationInfo = null; + Map dataMap = jsonObject.toHashMap(); + Map messageMetadata = null; + String applicationId = null; + boolean shouldMaintainSession = true; + + if (jsonObject.hasKey(KM_USER)) { + user = (KMUser) GsonUtils.getObjectFromJson(jsonObject.getString(KM_USER), KMUser.class); + dataMap.remove(KM_USER); + } + + if (jsonObject.hasKey("shouldMaintainSession")) { + shouldMaintainSession = jsonObject.getBoolean("shouldMaintainSession"); + dataMap.remove("shouldMaintainSession"); + } + + if (user.getApplicationId() != null) { + applicationId = user.getApplicationId(); + } else if (jsonObject.hasKey(APP_ID)) { + applicationId = (String) jsonObject.getString(APP_ID); + } else { + callback.invoke(ERROR, "The object doesn't contain appId."); + } + + if (jsonObject.hasKey(CONVERSATION_INFO)) { + conversationInfo = (Map) GsonUtils.getObjectFromJson(jsonObject.getString(CONVERSATION_INFO), Map.class); + dataMap.remove(CONVERSATION_INFO); + } + + if (jsonObject.hasKey(MESSAGE_METADATA)) { + messageMetadata = (Map) GsonUtils.getObjectFromJson(jsonObject.getString(MESSAGE_METADATA), Map.class); + dataMap.remove(MESSAGE_METADATA); + } + + KmConversationBuilder conversationBuilder = (KmConversationBuilder) GsonUtils.getObjectFromJson(GsonUtils.getJsonFromObject(dataMap, HashMap.class), KmConversationBuilder.class); + conversationBuilder.setContext(currentActivity); + + if (!jsonObject.hasKey("isSingleConversation")) { + conversationBuilder.setSingleConversation(true); + } + if (!jsonObject.hasKey("skipConversationList")) { + conversationBuilder.setSkipConversationList(true); + } + if (user != null) { + conversationBuilder.setKmUser(user); + } + if (conversationInfo != null) { + conversationBuilder.setConversationInfo(conversationInfo); + } + if (messageMetadata != null) { + conversationBuilder.setMessageMetadata(messageMetadata); + } + // Launch Kommunicate conversation + Kommunicate.launchConversationWithUser( + currentActivity, + applicationId, + user, + conversationBuilder, + shouldMaintainSession, + new KmCallback() { + @Override + public void onSuccess(Object message) { + callback.invoke(SUCCESS, message != null ? ChannelService.getInstance(currentActivity).getChannelByChannelKey((Integer) message).getClientGroupId() : "Success"); + } + + @Override + public void onFailure(Object error) { + callback.invoke(ERROR, error != null ? error.toString() : "Unknown error occurred"); + } + } + ); + } catch (Exception e) { + callback.invoke(ERROR, e.toString()); + } + } + @ReactMethod public void updateUserDetails(final ReadableMap config, final Callback callback) { final Activity currentActivity = getCurrentActivity(); diff --git a/ios/RNKommunicateChat.swift b/ios/RNKommunicateChat.swift index 895870d..d33acc2 100644 --- a/ios/RNKommunicateChat.swift +++ b/ios/RNKommunicateChat.swift @@ -12,7 +12,7 @@ import KommunicateChatUI_iOS_SDK import KommunicateCore_iOS_SDK import React -@objc (RNKommunicateChat) +@objc(RNKommunicateChat) class RNKommunicateChat : RCTEventEmitter, KMPreChatFormViewControllerDelegate, KMChatCustomEventCallback { public static var emitter: RCTEventEmitter! @@ -99,6 +99,104 @@ class RNKommunicateChat : RCTEventEmitter, KMPreChatFormViewControllerDelegate, }) } + @objc + func launchConversationWithUser(_ jsonObj: Dictionary, _ callback: @escaping RCTResponseSenderBlock)-> Void { + do { + // Extract values with defaults + let appId = jsonObj["appId"] as? String + let shouldMaintainSession = jsonObj["shouldMaintainSession"] as? Bool ?? true + let isSingleConversation = jsonObj["isSingleConversation"] as? Bool ?? true + let createOnly = jsonObj["createOnly"] as? Bool ?? false + let conversationAssignee = jsonObj["conversationAssignee"] as? String + let clientConversationId = jsonObj["clientConversationId"] as? String + let teamId = jsonObj["teamId"] as? String + let conversationTitle = jsonObj["conversationTitle"] as? String + let agentIds = jsonObj["agentIds"] as? [String] + let botIds = jsonObj["botIds"] as? [String] + let messageMetadata = jsonObj["messageMetadata"] as? [String: Any] + let conversationInfo = jsonObj["conversationInfo"].map { + [RNKommunicateChat.KM_CONVERSATION_METADATA: $0] + } + + // Prepare KMUser if provided + var kmUser: KMUser? + + if let kmUserEncoded = jsonObj["kmUser"] as? String { + let decodedStr = kmUserEncoded.replacingOccurrences(of: "\\\"", with: "\"") + kmUser = KMUser(jsonString: decodedStr) + } else if let kmUserDict = jsonObj["kmUser"] as? [String: Any] { + kmUser = parseKMUser(from: kmUserDict) + } + + if let user = kmUser { + user.applicationId = appId + user.platform = NSNumber(value: PLATFORM_FLUTTER.rawValue) + } + + // Set metadata if present + if let metadata = messageMetadata { + Kommunicate.defaultConfiguration.messageMetadata = metadata + } + + // Build the conversation + let builder = KMConversationBuilder() + .useLastConversation(isSingleConversation) + + if let agents = agentIds, !agents.isEmpty { + builder.withAgentIds(agents) + } + + if let bots = botIds, !bots.isEmpty { + builder.withBotIds(bots) + } + + if let assignee = conversationAssignee { + builder.withConversationAssignee(assignee) + } + + if let clientId = clientConversationId { + builder.withClientConversationId(clientId) + } + + if let team = teamId { + builder.withTeamId(team) + } + + if let title = conversationTitle { + builder.withConversationTitle(title) + } + + if let info = conversationInfo { + builder.withMetaData(info) + } + + // Launch conversation on main thread + DispatchQueue.main.async { + guard let topVC = UIApplication.topViewController() else { + callback(["Error", "Error getting ViewController"]) + return + } + + Kommunicate.launchConversationWithUser( + appID: appId, + kmUser: kmUser, + from: topVC, + conversation: builder.build(), + shouldMaintainSession: shouldMaintainSession + ) { response in + switch response { + case .success(let conversationID): + callback(["Success", conversationID]) + case .failure(let error): + callback(["Error", "Error launching conversation: \(error)"]) + } + } + } + } catch let error as NSError { + callback(["Error", "Failed to process object: \(error.localizedDescription)"]) + } + } + @objc func sendMessage(_ jsonObj: Dictionary, _ callback: @escaping RCTResponseSenderBlock) -> Void { do { @@ -751,27 +849,27 @@ class RNKommunicateChat : RCTEventEmitter, KMPreChatFormViewControllerDelegate, } func attachmentOptionClicked(attachemntType: String) { - KMEventEmitter.emitter.sendEvent(withName: "onAttachmentOptionClicked", body: ["data": attachemntType]) + KMEventEmitter.emitter.sendEvent(withName: "onAttachmentClick", body: ["data": attachemntType]) } func voiceButtonClicked(currentState: KommunicateChatUI_iOS_SDK.KMVoiceRecordingState) { - KMEventEmitter.emitter.sendEvent(withName: "onVoiceButtonClicked", body: ["data": "\(currentState.rawValue)"]) + KMEventEmitter.emitter.sendEvent(withName: "onVoiceButtonClick", body: ["data": "\(currentState.rawValue)"]) } func locationButtonClicked() { - KMEventEmitter.emitter.sendEvent(withName: "onLocationButtonClicked", body: nil) + KMEventEmitter.emitter.sendEvent(withName: "onLocationClick", body: nil) } func rateConversationEmotionsClicked(rating: Int) { - KMEventEmitter.emitter.sendEvent(withName: "onRateConversationEmotionClicked", body: ["data": rating]) + KMEventEmitter.emitter.sendEvent(withName: "onRatingEmoticonsClick", body: ["data": rating]) } func cameraButtonClicked() { - KMEventEmitter.emitter.sendEvent(withName: "onCameraButtonClicked", body: nil) + KMEventEmitter.emitter.sendEvent(withName: "onCameraClicked", body: nil) } func videoButtonClicked() { - KMEventEmitter.emitter.sendEvent(withName: "onVideoButtonClicked", body: nil) + KMEventEmitter.emitter.sendEvent(withName: "onVideoClicked", body: nil) } func convertDictToString(dict: NSDictionary) -> String { @@ -780,6 +878,90 @@ class RNKommunicateChat : RCTEventEmitter, KMPreChatFormViewControllerDelegate, } return String(data:data, encoding:.utf8) ?? "" } + + func parseKMUser(from dictionary: [String: Any]) -> KMUser { + let user = KMUser() + + user.userId = dictionary["userId"] as? String + user.email = dictionary["email"] as? String + user.password = dictionary["password"] as? String + user.displayName = dictionary["displayName"] as? String + user.registrationId = dictionary["registrationId"] as? String + user.applicationId = dictionary["applicationId"] as? String + user.contactNumber = dictionary["contactNumber"] as? String + user.countryCode = dictionary["countryCode"] as? String + + if let prefAPI = dictionary["prefContactAPI"] as? Int16 { + user.prefContactAPI = prefAPI + } + + if let verified = dictionary["emailVerified"] as? Bool { + user.emailVerified = verified + } + + user.timezone = dictionary["timezone"] as? String + + if let versionCode = dictionary["appVersionCode"] as? Int16 { + user.appVersionCode = versionCode + } + + user.roleName = dictionary["roleName"] as? String + + if let deviceType = dictionary["deviceType"] as? Int16 { + user.deviceType = deviceType + } + + user.imageLink = dictionary["imageLink"] as? String + user.appModuleName = dictionary["appModuleName"] as? String + + if let userTypeId = dictionary["userTypeId"] as? Int16 { + user.userTypeId = userTypeId + } + + if let notifMode = dictionary["notificationMode"] as? Int16 { + user.notificationMode = notifMode + } + + if let authId = dictionary["authenticationTypeId"] as? Int16 { + user.authenticationTypeId = authId + } + + if let unreadType = dictionary["unreadCountType"] as? Int16 { + user.unreadCountType = unreadType + } + + if let apnsType = dictionary["deviceApnsType"] as? Int16 { + user.deviceApnsType = apnsType + } + + if let pushFormat = dictionary["pushNotificationFormat"] as? Int16 { + user.pushNotificationFormat = pushFormat + } + + if let encryption = dictionary["enableEncryption"] as? Bool { + user.enableEncryption = encryption + } + + if let contactType = dictionary["contactType"] as? NSNumber { + user.contactType = contactType + } + + if let features = dictionary["features"] as? [Any] { + user.features = NSMutableArray(array: features) + } + + user.notificationSoundFileName = dictionary["notificationSoundFileName"] as? String + + if let metadata = dictionary["metadata"] as? [String: Any] { + user.metadata = NSMutableDictionary(dictionary: metadata) + } + + if let platform = dictionary["platform"] as? NSNumber { + user.platform = platform + } + + return user + } @objc func closeConversationScreen() -> Void { From 04dcde786c742bcc7fa57a779cd29f336631b1c1 Mon Sep 17 00:00:00 2001 From: Abhijeet Ranjan Date: Wed, 30 Jul 2025 19:15:51 +0530 Subject: [PATCH 2/2] Fix appId handling and platform assignment in chat modules Added null check for user before accessing applicationId in Android module and ensured early return on missing appId. Updated iOS module to assign correct platform value for React Native users. --- .../main/java/io/kommunicate/app/RNKommunicateChatModule.java | 3 ++- ios/RNKommunicateChat.swift | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/android/src/main/java/io/kommunicate/app/RNKommunicateChatModule.java b/android/src/main/java/io/kommunicate/app/RNKommunicateChatModule.java index f1c1015..5b3c09f 100755 --- a/android/src/main/java/io/kommunicate/app/RNKommunicateChatModule.java +++ b/android/src/main/java/io/kommunicate/app/RNKommunicateChatModule.java @@ -243,12 +243,13 @@ public void launchConversationWithUser(final ReadableMap jsonObject, final Callb dataMap.remove("shouldMaintainSession"); } - if (user.getApplicationId() != null) { + if (user != null && user.getApplicationId() != null) { applicationId = user.getApplicationId(); } else if (jsonObject.hasKey(APP_ID)) { applicationId = (String) jsonObject.getString(APP_ID); } else { callback.invoke(ERROR, "The object doesn't contain appId."); + return; } if (jsonObject.hasKey(CONVERSATION_INFO)) { diff --git a/ios/RNKommunicateChat.swift b/ios/RNKommunicateChat.swift index d33acc2..9203751 100644 --- a/ios/RNKommunicateChat.swift +++ b/ios/RNKommunicateChat.swift @@ -130,7 +130,7 @@ class RNKommunicateChat : RCTEventEmitter, KMPreChatFormViewControllerDelegate, if let user = kmUser { user.applicationId = appId - user.platform = NSNumber(value: PLATFORM_FLUTTER.rawValue) + user.platform = 7 // 7 is for React Native } // Set metadata if present