From 7f870b5cf977645b9cce927f28137ecaf17b93ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81bastien=20Vray?= Date: Mon, 21 Oct 2024 16:09:11 +0200 Subject: [PATCH] Re-coded module using Zendesk SDK Messaging --- .gitignore | 3 - ReactNativeZendesk.podspec | 7 +- android/build.gradle | 8 +- android/src/main/AndroidManifest.xml | 5 +- .../reactNativeZendesk/EventsEmitter.kt | 28 -- .../reactNativeZendesk/PushListenerService.kt | 91 +----- .../ReactNativeZendeskModule.kt | 217 +++++++------ .../ReactNativeZendeskPackage.kt | 13 +- .../ReactNativeZendesk.swift | 299 ++++++++++++------ .../UserInterfaceState.xcuserstate | Bin 26434 -> 28472 bytes .../ReactNativeZendeskBridge.h | 21 +- lib/commonjs/index.js | 53 ++++ lib/commonjs/index.js.map | 1 + lib/module/index.js | 41 +++ lib/module/index.js.map | 1 + lib/typescript/src/index.d.ts | 44 +++ lib/typescript/src/index.d.ts.map | 1 + src/index.ts | 83 +++-- 18 files changed, 547 insertions(+), 369 deletions(-) delete mode 100644 android/src/main/java/fr/wavyapp/reactNativeZendesk/EventsEmitter.kt create mode 100644 lib/commonjs/index.js create mode 100644 lib/commonjs/index.js.map create mode 100644 lib/module/index.js create mode 100644 lib/module/index.js.map create mode 100644 lib/typescript/src/index.d.ts create mode 100644 lib/typescript/src/index.d.ts.map diff --git a/.gitignore b/.gitignore index 623a18e..3c3629e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1 @@ node_modules - -# generated by bob -lib/ diff --git a/ReactNativeZendesk.podspec b/ReactNativeZendesk.podspec index a0ff274..87ed0c6 100644 --- a/ReactNativeZendesk.podspec +++ b/ReactNativeZendesk.podspec @@ -10,14 +10,13 @@ Pod::Spec.new do |s| s.authors = package['author'] s.homepage = package['repository'] - s.platform = :ios, "12.0" - s.ios.deployment_target = '12.0' + s.platform = :ios, "13.0" + s.ios.deployment_target = '13.0' s.source = { :git => "https://github.com/wavyapp/react-native-zendesk.git", :tag => "v#{s.version}" } s.source_files = "ios/**/*.{h,m,swift}" - s.dependency 'ZendeskChatSDK', '~> 3.0' - s.dependency 'ZendeskSupportSDK', '~> 6.0' + s.dependency 'ZendeskSDKMessaging', '~> 2.0' # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0. # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79. if respond_to?(:install_modules_dependencies, true) diff --git a/android/build.gradle b/android/build.gradle index daab805..cba0422 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -57,9 +57,7 @@ dependencies { implementation 'com.facebook.react:react-android:+' implementation "androidx.core:core-ktx:1.13.1" implementation "androidx.appcompat:appcompat:1.7.0" - // implementation platform('com.google.firebase:firebase-bom:33.1.1') - api "com.zendesk:support:5.2.0" - api "com.zendesk:messaging:5.4.0" - api "com.zendesk:chat:3.4.0" - implementation 'com.google.firebase:firebase-messaging-ktx:24.0.0' + implementation platform('com.google.firebase:firebase-bom:33.4.0') + implementation 'com.google.firebase:firebase-messaging' + implementation "zendesk.messaging:messaging-android:2.25.0" } diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index 25c1def..9cf3983 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -3,11 +3,10 @@ - + - \ No newline at end of file + diff --git a/android/src/main/java/fr/wavyapp/reactNativeZendesk/EventsEmitter.kt b/android/src/main/java/fr/wavyapp/reactNativeZendesk/EventsEmitter.kt deleted file mode 100644 index 52d6209..0000000 --- a/android/src/main/java/fr/wavyapp/reactNativeZendesk/EventsEmitter.kt +++ /dev/null @@ -1,28 +0,0 @@ -package fr.wavyapp.reactNativeZendesk - -typealias Callback = (data: Any) -> Unit - -class EventsEmitter { - private val subscribers: MutableMap> = emptyMap>().toMutableMap() - - @Suppress("UNUSED") - fun subscribe(event: String, callback: Callback): () -> Unit { - subscribers[event] = subscribers.getOrDefault(event, emptyList().toMutableList()) - subscribers[event]?.add(callback) - - return fun() { - subscribers[event]?.remove(callback) - } - } - - @Suppress("unused") - fun dispatchEvent(event: String, data: Any) { - subscribers[event]?.forEach { - it(data) - } - } - - companion object { - val instance: EventsEmitter = EventsEmitter() - } -} \ No newline at end of file diff --git a/android/src/main/java/fr/wavyapp/reactNativeZendesk/PushListenerService.kt b/android/src/main/java/fr/wavyapp/reactNativeZendesk/PushListenerService.kt index b1d7313..e4a1ba6 100644 --- a/android/src/main/java/fr/wavyapp/reactNativeZendesk/PushListenerService.kt +++ b/android/src/main/java/fr/wavyapp/reactNativeZendesk/PushListenerService.kt @@ -1,90 +1,33 @@ package fr.wavyapp.reactNativeZendesk -import android.app.NotificationChannel -import android.app.NotificationManager -import android.app.PendingIntent -import android.content.Intent -import android.os.Build import android.util.Log -import androidx.core.app.NotificationCompat import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage -import zendesk.chat.Chat -import zendesk.chat.ChatConfiguration -import zendesk.chat.ChatEngine -import zendesk.chat.PushData -import zendesk.classic.messaging.MessagingActivity +import zendesk.messaging.android.push.PushNotifications +import zendesk.messaging.android.push.PushResponsibility.MESSAGING_SHOULD_DISPLAY +import zendesk.messaging.android.push.PushResponsibility.MESSAGING_SHOULD_NOT_DISPLAY +import zendesk.messaging.android.push.PushResponsibility.NOT_FROM_MESSAGING -class PushListenerService() : FirebaseMessagingService() { +class PushListenerService : FirebaseMessagingService() { + + override fun onNewToken(token: String) { + super.onNewToken(token) + PushNotifications.updatePushNotificationToken(token) + } override fun onMessageReceived(remoteMessage: RemoteMessage) { super.onMessageReceived(remoteMessage) - val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager? - val zendeskChatPushNotificationProvider = Chat.INSTANCE.providers()?.pushNotificationsProvider() - - manager?.let { - val builder = - NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID) - .setSmallIcon(R.mipmap.ic_launcher) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channel = NotificationChannel( - NOTIFICATION_CHANNEL_ID, - NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH - ) - - channel.enableVibration(true) - channel.vibrationPattern = longArrayOf(100, 200, 100, 200) - builder.setChannelId(NOTIFICATION_CHANNEL_ID) - - val chatConfig = ChatConfiguration.builder() - chatConfig.withPreChatFormEnabled(false) - - val intent = MessagingActivity.builder().withEngines(ChatEngine.engine()).intent( - applicationContext, - chatConfig.build() - ) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - - val contentIntent = PendingIntent.getActivity( - this, - 0, - intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) + val shouldDisplay = PushNotifications.shouldBeDisplayed(remoteMessage.data) - builder.setContentIntent(contentIntent) - manager.createNotificationChannel(channel) + when (shouldDisplay) { + MESSAGING_SHOULD_DISPLAY -> { + PushNotifications.setNotificationSmallIconId(R.mipmap.ic_launcher) + PushNotifications.displayNotification(context = this, messageData = remoteMessage.data) } + MESSAGING_SHOULD_NOT_DISPLAY -> {} + NOT_FROM_MESSAGING -> { - val pushData = zendeskChatPushNotificationProvider?.processPushNotification(remoteMessage.data) - - when (pushData?.type) { - PushData.Type.END -> { - builder.setContentTitle("Chat ended") - builder.setContentText("The chat has ended!") - } - - PushData.Type.MESSAGE -> { - builder.setContentTitle(pushData.author) - builder.setContentText(pushData.message) - EventsEmitter.instance.dispatchEvent("chatReceivedMessage", pushData) - } - - else -> return } - manager.notify(NOTIFICATION_ID, builder.build()) - } ?: { - Log.d(LOG_TAG, "Notification manager not found") } } - - companion object { - private const val LOG_TAG = "PushListenerService" - - private val NOTIFICATION_ID = System.currentTimeMillis().toInt() - - private const val NOTIFICATION_CHANNEL_ID = "ZendeskChat" - private const val NOTIFICATION_CHANNEL_NAME = "Zendesk chat notifications" - } } diff --git a/android/src/main/java/fr/wavyapp/reactNativeZendesk/ReactNativeZendeskModule.kt b/android/src/main/java/fr/wavyapp/reactNativeZendesk/ReactNativeZendeskModule.kt index eb9c883..b294b07 100644 --- a/android/src/main/java/fr/wavyapp/reactNativeZendesk/ReactNativeZendeskModule.kt +++ b/android/src/main/java/fr/wavyapp/reactNativeZendesk/ReactNativeZendeskModule.kt @@ -1,45 +1,79 @@ package fr.wavyapp.reactNativeZendesk -import android.content.Intent +import android.app.Activity +import android.app.Application +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import android.os.Bundle import com.facebook.react.bridge.Promise import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod import com.facebook.react.bridge.ReadableMap -import com.facebook.react.bridge.WritableNativeMap import com.facebook.react.modules.core.DeviceEventManagerModule -import com.zendesk.service.ErrorResponse -import com.zendesk.service.ZendeskCallback import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch -import zendesk.chat.Chat -import zendesk.chat.ChatConfiguration -import zendesk.chat.ChatEngine -import zendesk.chat.PushData -import zendesk.chat.VisitorInfo -import zendesk.classic.messaging.MessagingActivity -import zendesk.core.AnonymousIdentity -import zendesk.core.Zendesk -import zendesk.support.Support - -class ReactNativeZendeskModule(reactContext: ReactApplicationContext, private val firebaseMessagingToken: String?) : ReactContextBaseJavaModule(reactContext) { +import zendesk.android.Zendesk +import zendesk.android.ZendeskResult +import zendesk.android.events.ZendeskEvent +import zendesk.android.events.ZendeskEventListener +import zendesk.messaging.android.DefaultMessagingFactory + +class ReactNativeZendeskModule(private val reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { private var isInit = false private val zendeskCoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) private var listeners = 0 override fun initialize() { super.initialize() - EventsEmitter.instance.subscribe("chatReceivedMessage") { - (it as? PushData)?.let { messageData -> - val nativeMap = WritableNativeMap() - nativeMap.putString("agentName", messageData.author) - nativeMap.putString("message", messageData.message) - sendEvent("zendeskChatReceivedMessage", nativeMap) + reactApplicationContext.currentActivity?.application?.registerActivityLifecycleCallbacks(object: Application.ActivityLifecycleCallbacks { + override fun onActivityPaused(activity: Activity) { + } + + override fun onActivityStarted(activity: Activity) { + if (activity.localClassName == "zendesk.messaging.android.internal.conversationscreen.ConversationActivity") { + sendEvent("zendeskMessagingOpened") + } + } + + override fun onActivityDestroyed(activity: Activity) { + // noop + } + + override fun onActivitySaveInstanceState(activity: Activity, p1: Bundle) { + // noop + } + + override fun onActivityStopped(activity: Activity) { + if (activity.localClassName == "zendesk.messaging.android.internal.conversationscreen.ConversationActivity") { + sendEvent("zendeskMessagingClosed") + } + } + + override fun onActivityCreated(activity: Activity, p1: Bundle?) { + // noop + } + + override fun onActivityResumed(activity: Activity) { + // noop + } + }) + } + + private fun addZendeskEventsObservers() { + val eventListener = ZendeskEventListener { + event -> when (event) { + is ZendeskEvent.AuthenticationFailed -> sendEvent("zendeskMessagingAuthenticationFailed") + is ZendeskEvent.ConversationAdded -> sendEvent("zendeskMessagingConversationAdded") + is ZendeskEvent.ConnectionStatusChanged -> sendEvent("zendeskMessagingConnectionStatusChanged") + is ZendeskEvent.FieldValidationFailed -> {} + is ZendeskEvent.SendMessageFailed -> sendEvent("zendeskMessagingSendMessageFailed") + is ZendeskEvent.UnreadMessageCountChanged -> sendEvent("zendeskMessagingUnreadCountChanged", event.currentUnreadCount) } } + Zendesk.instance.addEventListener(eventListener) } override fun getName(): String { @@ -64,122 +98,95 @@ class ReactNativeZendeskModule(reactContext: ReactApplicationContext, private va @Suppress("unused") @ReactMethod - fun initialize( - zendeskUrl: String, - appId: String, - clientId: String, - chatAppId: String?, - chatAccountKey: String?, + fun initializeSDK( + channelKey: String, promise: Promise ) { zendeskCoroutineScope.launch { - Zendesk.INSTANCE.init(reactApplicationContext, zendeskUrl, appId, clientId) - Support.INSTANCE.init(Zendesk.INSTANCE) - - if (chatAppId != null && chatAccountKey != null) { - Chat.INSTANCE.init(reactApplicationContext, chatAccountKey, chatAppId) - firebaseMessagingToken?.let { - Chat.INSTANCE.providers()?. - pushNotificationsProvider()?. - registerPushToken(firebaseMessagingToken) + val init = Zendesk.initialize( + context = reactApplicationContext, + channelKey = channelKey, + messagingFactory = DefaultMessagingFactory(), + ) + + when (init) { + is ZendeskResult.Success -> { + isInit = true + addZendeskEventsObservers() + promise.resolve(true) } + is ZendeskResult.Failure -> promise.reject("FAILED_TO_INIT_MESSAGING_SDK", init.error) + } + } + } + + @Suppress("unused") + @ReactMethod + fun logUserIn(JWT: String, promise: Promise) { + if (!isInit) { + promise.reject("LOG_USER_IN_MUST_INIT_SDK", "Call the initialize method first", null) + } + + zendeskCoroutineScope.launch { + when (val loginResult = Zendesk.instance.loginUser(JWT)) { + is ZendeskResult.Success -> promise.resolve(true) + is ZendeskResult.Failure -> promise.reject("LOGIN_USER_IN_FAILURE", loginResult.error) } - isInit = true - promise.resolve(true) } } @Suppress("unused") @ReactMethod - fun identifyUser(identityTraits: ReadableMap, promise: Promise) { + fun logUserOut(promise: Promise) { if (!isInit) { - promise.reject("IDENTIFY_USER_MUST_INIT_SDK", "Call the initialize method first", null) + promise.reject("LOG_USER_OUT_MUST_INIT_SDK", "Call the initialize method first", null) } - val identity = AnonymousIdentity.Builder() - identityTraits.getString("name")?.let { identity.withNameIdentifier(it) } - identityTraits.getString("email")?.let { identity.withEmailIdentifier(it) } + zendeskCoroutineScope.launch { + when (val logoutResult = Zendesk.instance.logoutUser()) { + is ZendeskResult.Success -> promise.resolve(true) + is ZendeskResult.Failure -> promise.reject("LOGIN_USER_OUT_FAILURE", logoutResult.error) + } + } + } - Zendesk.INSTANCE.setIdentity(identity.build()) + @Suppress("unused") + @ReactMethod + fun close(promise: Promise) { + // Just resolve the promise without doing anything since when the conversation activity is started the JS is not executed, so this methods can't be called anyway promise.resolve(true) } @Suppress("unused") @ReactMethod - fun openChat( - userInfos: ReadableMap?, - chatOpts: ReadableMap?, + fun open( + metadata: ReadableMap?, promise: Promise ) { if (!isInit) { - promise.reject("OPEN_CHAT_MUST_INIT_SDK", "Call the initialize method first", null) + promise.reject("OPEN_MUST_INIT_SDK", "Call the initialize method first", null) } - userInfos?.let {chatUserInfos -> - val visitorInfo = VisitorInfo.builder() - chatUserInfos.getString("name")?.let { visitorInfo.withName(it) } - chatUserInfos.getString("email")?.let { visitorInfo.withEmail(it) } - chatUserInfos.getString("phone")?.let { visitorInfo.withPhoneNumber(it) } + metadata?.let { notNullMetadata -> + val tags = notNullMetadata.getArray("tags")?.toArrayList()?.filterIsInstance() + tags?.let { Zendesk.instance.messaging.setConversationTags(it) } - val chatProfileProvider = Chat.INSTANCE.providers()?.profileProvider() + val conversationFields = notNullMetadata.getArray("fields")?.toArrayList() - chatProfileProvider?.setVisitorInfo(visitorInfo.build(), object : ZendeskCallback() { - override fun onError(error: ErrorResponse?) { - promise.reject("OPEN_CHAT_FAILED_TO_SET_VISITOR_INFO", error.toString()) - } - override fun onSuccess(result: Void?) { - // do nothing + conversationFields?.filterIsInstance>()?.let { notNullConversationFields -> + val conversationFieldsFormatIsValid = notNullConversationFields.all { + it.contains("id") && it.contains("value") && it["id"] is String && it["value"] != null } - }) - - chatUserInfos.getArray("tags")?.let { - if (it.size() > 0) { - chatProfileProvider?.addVisitorTags( - it.toArrayList().filterIsInstance().toMutableList(), - object : ZendeskCallback() { - override fun onError(error: ErrorResponse?) { - promise.reject("OPEN_CHAT_FAILED_TO_SET_VISITOR_TAGS", error.toString()) - } - override fun onSuccess(result: Void?) { - // do nothing - } - } - ) + if (!conversationFieldsFormatIsValid) { + return promise.reject("OPEN_MUST_MALFORMED_CONVERSATION_FIELDS", "`fields` parameter should be of the form [{ id: string, value: String | number | boolean}]") } - } - } - val chatConfig = ChatConfiguration.builder() - chatOpts?.let { - if (chatOpts.hasKey("enableAgentAvailability")) { - chatConfig.withAgentAvailabilityEnabled( - chatOpts.getBoolean("enableAgentAvailability") - ) - } - if (chatOpts.hasKey("enablePreChatForm")) { - chatConfig.withPreChatFormEnabled( - chatOpts.getBoolean("enablePreChatForm") - ) - } - if (chatOpts.hasKey("enableTranscript")) { - chatConfig.withTranscriptEnabled( - chatOpts.getBoolean("enableTranscript") - ) - } - if (chatOpts.hasKey("enableOfflineForm")) { - chatConfig.withOfflineFormEnabled( - chatOpts.getBoolean("enableOfflineForm") - ) + Zendesk.instance.messaging.setConversationFields(notNullConversationFields.associate { Pair(it["id"] as String, it["value"] as Any) }) } } - val intent = MessagingActivity.builder().withEngines(ChatEngine.engine()).intent( - reactApplicationContext, - chatConfig.build() - ) + Zendesk.instance.messaging.showMessaging(reactApplicationContext, FLAG_ACTIVITY_NEW_TASK) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK - reactApplicationContext.startActivity(intent) promise.resolve(true) } @@ -190,4 +197,4 @@ class ReactNativeZendeskModule(reactContext: ReactApplicationContext, private va .emit(name, body) } } -} \ No newline at end of file +} diff --git a/android/src/main/java/fr/wavyapp/reactNativeZendesk/ReactNativeZendeskPackage.kt b/android/src/main/java/fr/wavyapp/reactNativeZendesk/ReactNativeZendeskPackage.kt index 1ed6ef7..493c78e 100644 --- a/android/src/main/java/fr/wavyapp/reactNativeZendesk/ReactNativeZendeskPackage.kt +++ b/android/src/main/java/fr/wavyapp/reactNativeZendesk/ReactNativeZendeskPackage.kt @@ -8,20 +8,11 @@ import com.facebook.react.uimanager.ReactShadowNode import com.facebook.react.uimanager.ViewManager class ReactNativeZendeskPackage : ReactPackage { - private var firebaseMessagingToken: String? = null - override fun createViewManagers( context: ReactApplicationContext ): MutableList>> = mutableListOf() override fun createNativeModules( context: ReactApplicationContext - ): MutableList = listOf(ReactNativeZendeskModule(context, firebaseMessagingToken)).toMutableList() - - @Suppress("unused") - fun setFirebaseMessagingToken(token: String?) { - if (token != null) { - firebaseMessagingToken = token - } - } -} \ No newline at end of file + ): MutableList = listOf(ReactNativeZendeskModule(context)).toMutableList() +} diff --git a/ios/ReactNativeZendesk/ReactNativeZendesk.swift b/ios/ReactNativeZendesk/ReactNativeZendesk.swift index a94c1f7..7ed7afb 100644 --- a/ios/ReactNativeZendesk/ReactNativeZendesk.swift +++ b/ios/ReactNativeZendesk/ReactNativeZendesk.swift @@ -1,13 +1,12 @@ -import ChatSDK -import ChatProvidersSDK -import MessagingSDK -import ZendeskCoreSDK +import ZendeskSDK +import ZendeskSDKMessaging import UserNotifications @objc(ReactNativeZendesk) -class ReactNativeZendesk: RCTEventEmitter, UNUserNotificationCenterDelegate { +class ReactNativeZendesk: RCTEventEmitter, UNUserNotificationCenterDelegate, UIAdaptivePresentationControllerDelegate { var isInit = false - + var hasEventListeners = false + override init() { super.init() let notificationCenter = UNUserNotificationCenter.current() @@ -19,138 +18,232 @@ class ReactNativeZendesk: RCTEventEmitter, UNUserNotificationCenterDelegate { } } } - + + override func startObserving() { + self.hasEventListeners = true + } + + override func stopObserving() { + self.hasEventListeners = false + } + + override func sendEvent(withName name: String!, body: Any!) { + if (self.hasEventListeners) { + super.sendEvent(withName: name, body: body) + } + } + override func supportedEvents() -> [String]! { return [ - "zendeskChatReceivedMessage", + "zendeskMessagingAuthenticationFailed", + "zendeskMessagingConversationAdded", + "zendeskMessagingConnectionStatusChanged", + "zendeskMessagingReceivedMessage", + "zendeskMessagingSendMessageFailed", + "zendeskMessagingUnreadCountChanged", + "zendeskMessagingClosed", + "zendeskMessagingOpened", ] } - - func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + + func userNotificationCenter( + _ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void + ) { let userInfo = notification.request.content.userInfo - let application = UIApplication.shared - Chat.didReceiveRemoteNotification(userInfo, in: application) - - if - let notificationData = userInfo["data"] as? NSDictionary, - let notificationType = notificationData["type"] as? String, - notificationType == "zd.chat.msg", - let aps = userInfo["aps"] as? NSDictionary, - let alert = aps["alert"] as? NSDictionary, - let agentName = alert["title"] as? String, - let message = alert["body"] as? String - { - sendEvent(withName: "zendeskChatReceivedMessage", body: ["message": message, "agentName": agentName]) + let shouldBeDisplayed = PushNotifications.shouldBeDisplayed(userInfo) + + switch shouldBeDisplayed { + case .messagingShouldDisplay: + if + let message = userInfo["message"] as? Dictionary, + let agentName = message["name"] as? String, + let content = message["text"] as? String + { + sendEvent(withName: "zendeskMessagingReceivedMessage", body: ["message": content, "agentName": agentName]) + } + // This push belongs to Messaging and the SDK is able to display it to the end user + if #available(iOS 14.0, *) { + completionHandler([.banner, .sound, .badge]) + } else { + completionHandler([.alert, .sound, .badge]) + } + case .messagingShouldNotDisplay: + // This push belongs to Messaging but it should not be displayed to the end user + completionHandler([]) + case .notFromMessaging: + // This push does not belong to Messaging + // If not, just call the `completionHandler` + completionHandler([.alert, .sound, .badge]) + @unknown default: break } - completionHandler([.alert, .sound, .badge]) } - - func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) { - Swift.print(response.actionIdentifier) + + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + let shouldBeDisplayed = PushNotifications.shouldBeDisplayed(response.notification.request.content.userInfo) + + switch shouldBeDisplayed { + case .messagingShouldDisplay: + PushNotifications.handleTap(response.notification.request.content.userInfo) { viewController in + DispatchQueue.main.async { + guard let presentedVc = RCTPresentedViewController() else { + return + } + guard let notNilViewController = viewController else { + return + } + notNilViewController.presentationController?.delegate = self + presentedVc.present(notNilViewController, animated: true) { + self.sendEvent(withName: "zendeskMessagingOpened", body: nil) + } + } + } + default: break + } + completionHandler() + } + + func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + self.sendEvent(withName: "zendeskMessagingClosed", body: nil) + } + + func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool { + return true } - + + func addZendeskEventsObservers() { + Zendesk.instance?.addEventObserver(self) { event in + switch event { + case .unreadMessageCountChanged(let unreadCount): + self.sendEvent(withName: "zendeskMessagingUnreadCountChanged", body: unreadCount) + case .authenticationFailed(let error as NSError): + self.sendEvent(withName: "zendeskMessagingAuthenticationFailed", body: error.localizedDescription) + case .conversationAdded(conversationId: let conversationId): + self.sendEvent(withName: "zendeskMessagingConversationAdded", body: conversationId) + case .connectionStatusChanged(connectionStatus: let connectionStatus): + self.sendEvent(withName: "zendeskMessagingConnectionStatusChanged", body: connectionStatus) + case .sendMessageFailed(let error as NSError): + self.sendEvent(withName: "zendeskMessagingSendMessageFailed", body: error.localizedDescription) + @unknown default: + break + } + } + + } + @objc - func initialize( - _ zendeskUrl: String, - appId: String, - clientId: String, - chatAppId: String?, - chatAccountKey: String?, + func initializeSDK( + _ channelKey: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock ) { - Zendesk.initialize(appId: appId, clientId: clientId, zendeskUrl: zendeskUrl) - - if let chatAppIdNotNil = chatAppId, let chatAccountKeyNotNil = chatAccountKey { - Chat.initialize(accountKey: chatAccountKeyNotNil, appId: chatAppIdNotNil) + Zendesk.initialize(withChannelKey: channelKey,messagingFactory: DefaultMessagingFactory()) { result in + if case let .failure(error) = result { + reject("FAILED_TO_INIT_MESSAGING_SDK", error.localizedDescription, nil) + } else { + self.isInit = true + self.addZendeskEventsObservers() + resolve(true) + } } - - isInit = true - resolve(true) } - + @objc - func identifyUser( - _ identityTraits: [String:Any], + func logUserIn( + _ JWT: String, resolver resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock ) { guard isInit else { - return reject("IDENTIFY_USER_MUST_INIT_SDK", "Call the initialize method first", nil) + return reject("LOG_USER_IN_MUST_INIT_SDK", "Call the initialize method first", nil) + } + Zendesk.instance?.loginUser(with: JWT) { result in + switch result { + case .success(let user): + resolve([ + "id": user.id, + "externalId": user.externalId, + ]); + case .failure(let error): + reject("LOGIN_USER_IN_FAILURE", error.localizedDescription, nil) + } } - - let identity = Identity.createAnonymous( - name: identityTraits["name"] as? String, - email: identityTraits["email"] as? String - ) - - Zendesk.instance?.setIdentity(identity) - resolve(true) } - + @objc - func openChat( - _ userInfos: [String:Any]?, - chatOpts: [String:Any]?, - resolver resolve: @escaping RCTPromiseResolveBlock, + func logUserOut( + _ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock ) { guard isInit else { - return reject("OPEN_CHAT_MUST_INIT_SDK", "Call the initialize method first", nil) + return reject("LOG_USER_OUT_MUST_INIT_SDK", "Call the initialize method first", nil) } - let messagingConfig = MessagingConfiguration() - let chatConfig = ChatConfiguration() - - if let notNilChatOpts = chatOpts { - if let enableAgentAvailability = notNilChatOpts["enableAgentAvailability"] as? Bool { - chatConfig.isAgentAvailabilityEnabled = enableAgentAvailability - } - if let enablePreChatForm = notNilChatOpts["enablePreChatForm"] as? Bool { - chatConfig.isPreChatFormEnabled = enablePreChatForm + Zendesk.instance?.logoutUser { result in + switch result { + case .success(): + resolve(true); + case .failure(let error): + reject("LOGIN_USER_OUT_FAILURE", error.localizedDescription, nil) } - if let enableTranscript = notNilChatOpts["enableTranscript"] as? Bool { - chatConfig.isChatTranscriptPromptEnabled = enableTranscript + } + } + + @objc + func close( + _ resolve: @escaping RCTPromiseResolveBlock, + rejecter reject: @escaping RCTPromiseRejectBlock + ) { + DispatchQueue.main.async { + guard let presentedVc = RCTPresentedViewController() else { + return reject("CLOSE_CANNOT_RETRIEVE_VC", "Could not retrieve the presented view controller", nil) } - if let enableOfflineForm = notNilChatOpts["enableOfflineForm"] as? Bool { - chatConfig.isOfflineFormEnabled = enableOfflineForm + presentedVc.dismiss(animated: true) { + self.sendEvent(withName: "zendeskMessagingClosed", body: nil) + resolve(true) } } - - if let notNilUserInfos = userInfos { - let visitorInfo = VisitorInfo( - name: notNilUserInfos["name"] as? String ?? "", - email: notNilUserInfos["email"] as? String ?? "", - phoneNumber: notNilUserInfos["phone"] as? String ?? "" - ) - Chat.profileProvider?.setVisitorInfo(visitorInfo) { result in - switch result { - case .failure(let error): - reject("OPEN_CHAT_FAILED_TO_SET_VISITOR_INFO", error.localizedDescription, error) - default: - break - } + } + + @objc + func open( + _ metadata: [String:Any]?, + resolver resolve: @escaping RCTPromiseResolveBlock, + rejecter reject: @escaping RCTPromiseRejectBlock + ) { + guard isInit else { + return reject("OPEN_MUST_INIT_SDK", "Call the initialize method first", nil) + } + + if let notNilMetadata = metadata { + if let tags = notNilMetadata["tags"] as? [String] { + Zendesk.instance?.messaging?.setConversationTags(tags) } - if let tags = notNilUserInfos["tags"] as? [String] { - Chat.profileProvider?.addTags(tags) + if let conversationFields = notNilMetadata["fields"] as? [[String: AnyHashable]] { + let conversationFieldsFormatIsValid = conversationFields.allSatisfy { fieldData in fieldData["id"] is String && fieldData["value"] != nil } + + guard conversationFieldsFormatIsValid else { + return reject("OPEN_MUST_MALFORMED_CONVERSATION_FIELDS", "`fields` parameter should be of the form [{ id: string, value: String | number | boolean}]", nil) + } + let conversationFieldsDict = Dictionary( + conversationFields.map { fieldData in (fieldData["id"] as! String, fieldData["value"]!)}, + uniquingKeysWith: { (_, last) in last } + ) + Zendesk.instance?.messaging?.setConversationFields(conversationFieldsDict) } } DispatchQueue.main.async { - do { - guard let presentedVc = RCTPresentedViewController() else { - return reject("OPEN_CHAT_CANNOT_RETRIEVE_VC", "Could not retrieve the presented view controller", nil) - } - let chatEngine = try ChatEngine.engine() - let navigationController = UINavigationController() - let chatViewController = try Messaging.instance.buildUI(engines: [chatEngine], configs: [chatConfig, messagingConfig]) - navigationController.pushViewController(chatViewController, animated: true) - presentedVc.present(navigationController, animated: true) { - resolve(true) - } - } catch ChatError.chatIsNotInitialized { - reject("OPEN_CHAT_MUST_INIT_CHAT", "Call the initialize method with appropriate parameters to initialize the chat first", nil) - } catch { - reject("OPEN_CHAT_UNKNOWN_ERROR", "Unknown error while trying to open the chat", error) + guard let presentedVc = RCTPresentedViewController() else { + return reject("OPEN_CANNOT_RETRIEVE_VC", "Could not retrieve the presented view controller", nil) + } + guard let messagingViewController = Zendesk.instance?.messaging?.messagingViewController() else { + return reject("OPEN_CANNOT_MESSAGING_VC", "Could not retrieve zendesk messaging view controller", nil) + } + messagingViewController.presentationController?.delegate = self + presentedVc.present(messagingViewController, animated: true) { + self.sendEvent(withName: "zendeskMessagingOpened", body: nil) + resolve(true) } } } diff --git a/ios/ReactNativeZendesk/ReactNativeZendesk.xcodeproj/project.xcworkspace/xcuserdata/serybva.xcuserdatad/UserInterfaceState.xcuserstate b/ios/ReactNativeZendesk/ReactNativeZendesk.xcodeproj/project.xcworkspace/xcuserdata/serybva.xcuserdatad/UserInterfaceState.xcuserstate index b092b8f2f60181c50795bb33f1f1f87bbaf96575..ba06e751d9a16da2e66b5e30f04c2ed4c775c05d 100644 GIT binary patch literal 28472 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 diff --git a/ios/ReactNativeZendesk/ReactNativeZendeskBridge.h b/ios/ReactNativeZendesk/ReactNativeZendeskBridge.h index b3cb045..9f86862 100644 --- a/ios/ReactNativeZendesk/ReactNativeZendeskBridge.h +++ b/ios/ReactNativeZendesk/ReactNativeZendeskBridge.h @@ -4,24 +4,29 @@ @interface RCT_EXTERN_MODULE(ReactNativeZendesk, RCTEventEmitter) RCT_EXTERN_METHOD( - initialize: (NSString) zendeskUrl - appId: (NSString) appId - clientId: (NSString) clientId - chatAppId: (NSString) chatAppId - chatAccountKey: (NSString) chatAccountKey + initializeSDK: (NSString) channelKey resolver: (RCTPromiseResolveBlock) resolve rejecter: (RCTPromiseRejectBlock) rejecter ) RCT_EXTERN_METHOD( - identifyUser: (id) identityTraits + logUserIn: (NSString) JWT resolver: (RCTPromiseResolveBlock) resolve rejecter: (RCTPromiseRejectBlock) rejecter ) RCT_EXTERN_METHOD( - openChat: (id) userInfos - chatOpts: (id) chatOpts + logUserOut: (RCTPromiseResolveBlock) resolve + rejecter: (RCTPromiseRejectBlock) rejecter + ) + +RCT_EXTERN_METHOD( + close: (RCTPromiseResolveBlock) resolve + rejecter: (RCTPromiseRejectBlock) rejecter + ) + +RCT_EXTERN_METHOD( + open: (id) metadata resolver: (RCTPromiseResolveBlock) resolve rejecter: (RCTPromiseRejectBlock) rejecter ) diff --git a/lib/commonjs/index.js b/lib/commonjs/index.js new file mode 100644 index 0000000..e86fa1e --- /dev/null +++ b/lib/commonjs/index.js @@ -0,0 +1,53 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.ZendeskEvent = void 0; +exports.addEventListener = addEventListener; +exports.close = close; +exports.initializeSDK = initializeSDK; +exports.logUserIn = logUserIn; +exports.logUserOut = logUserOut; +exports.open = open; +var _reactNative = require("react-native"); +const LINKING_ERROR = `The package '@wavyapp/react-native-zendesk' doesn't seem to be linked. Make sure: \n\n` + _reactNative.Platform.select({ + ios: "- You have run 'pod install'\n", + default: '' +}) + '- You rebuilt the app after installing the package\n'; +const ReactNativeZendesk = _reactNative.NativeModules.ReactNativeZendesk ? _reactNative.NativeModules.ReactNativeZendesk : new Proxy({}, { + get() { + throw new Error(LINKING_ERROR); + } +}); +const _nativeEventEmitter = new _reactNative.NativeEventEmitter(ReactNativeZendesk); +let ZendeskEvent = exports.ZendeskEvent = /*#__PURE__*/function (ZendeskEvent) { + ZendeskEvent["AUTHENTICATION_FAILED"] = "zendeskMessagingAuthenticationFailed"; + ZendeskEvent["CLOSED"] = "zendeskMessagingClosed"; + ZendeskEvent["CONVERSATION_ADDED"] = "zendeskMessagingConversationAdded"; + ZendeskEvent["CONNECTION_STATUS_CHANGED"] = "zendeskMessagingConnectionStatusChanged"; + ZendeskEvent["OPENED"] = "zendeskMessagingOpened"; + ZendeskEvent["RECEIVED_MESSAGE"] = "zendeskMessagingReceivedMessage"; + ZendeskEvent["SEND_MESSAGE_FAILED"] = "zendeskMessagingSendMessageFailed"; + ZendeskEvent["UNREAD_COUNT_CHANGED"] = "zendeskMessagingUnreadCountChanged"; + return ZendeskEvent; +}({}); +function initializeSDK(channelKey) { + return ReactNativeZendesk.initializeSDK(channelKey); +} +function logUserIn(JWT) { + return ReactNativeZendesk.logUserIn(JWT); +} +function logUserOut() { + return ReactNativeZendesk.logUserOut(); +} +function close() { + return ReactNativeZendesk.close(); +} +function open(metadata = null) { + return ReactNativeZendesk.open(metadata); +} +function addEventListener(event, callback) { + return _nativeEventEmitter.addListener(event, callback); +} +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/lib/commonjs/index.js.map b/lib/commonjs/index.js.map new file mode 100644 index 0000000..bf728cb --- /dev/null +++ b/lib/commonjs/index.js.map @@ -0,0 +1 @@ +{"version":3,"names":["_reactNative","require","LINKING_ERROR","Platform","select","ios","default","ReactNativeZendesk","NativeModules","Proxy","get","Error","_nativeEventEmitter","NativeEventEmitter","ZendeskEvent","exports","initializeSDK","channelKey","logUserIn","JWT","logUserOut","close","open","metadata","addEventListener","event","callback","addListener"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;;;;;;;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAEA,MAAMC,aAAa,GACjB,wFAAwF,GACxFC,qBAAQ,CAACC,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD;AAExD,MAAMC,kBAAkB,GAAGC,0BAAa,CAACD,kBAAkB,GACvDC,0BAAa,CAACD,kBAAkB,GAChC,IAAIE,KAAK,CACT,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACT,aAAa,CAAC;EAChC;AACF,CACF,CAAC;AAEH,MAAMU,mBAAmB,GAAG,IAAIC,+BAAkB,CAACN,kBAAkB,CAAC;AAAC,IAmB3DO,YAAY,GAAAC,OAAA,CAAAD,YAAA,0BAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAA,OAAZA,YAAY;AAAA;AAgBjB,SAASE,aAAaA,CAACC,UAAkB,EAAoB;EAClE,OAAOV,kBAAkB,CAACS,aAAa,CAACC,UAAU,CAAC;AACrD;AAEO,SAASC,SAASA,CAACC,GAAW,EAAwB;EAC3D,OAAOZ,kBAAkB,CAACW,SAAS,CAACC,GAAG,CAAC;AAC1C;AAEO,SAASC,UAAUA,CAAA,EAAqB;EAC7C,OAAOb,kBAAkB,CAACa,UAAU,CAAC,CAAC;AACxC;AAEO,SAASC,KAAKA,CAAA,EAAqB;EACxC,OAAOd,kBAAkB,CAACc,KAAK,CAAC,CAAC;AACnC;AAEO,SAASC,IAAIA,CAClBC,QAAyB,GAAG,IAAI,EACd;EAClB,OAAOhB,kBAAkB,CAACe,IAAI,CAACC,QAAQ,CAAC;AAC1C;AAUO,SAASC,gBAAgBA,CAC9BC,KAQmC,EACnCC,QAG0B,EACL;EACrB,OAAOd,mBAAmB,CAACe,WAAW,CAACF,KAAK,EAAEC,QAAQ,CAAC;AACzD","ignoreList":[]} diff --git a/lib/module/index.js b/lib/module/index.js new file mode 100644 index 0000000..bd9f0b9 --- /dev/null +++ b/lib/module/index.js @@ -0,0 +1,41 @@ +import { NativeEventEmitter, NativeModules, Platform } from 'react-native'; +const LINKING_ERROR = `The package '@wavyapp/react-native-zendesk' doesn't seem to be linked. Make sure: \n\n` + Platform.select({ + ios: "- You have run 'pod install'\n", + default: '' +}) + '- You rebuilt the app after installing the package\n'; +const ReactNativeZendesk = NativeModules.ReactNativeZendesk ? NativeModules.ReactNativeZendesk : new Proxy({}, { + get() { + throw new Error(LINKING_ERROR); + } +}); +const _nativeEventEmitter = new NativeEventEmitter(ReactNativeZendesk); +export let ZendeskEvent = /*#__PURE__*/function (ZendeskEvent) { + ZendeskEvent["AUTHENTICATION_FAILED"] = "zendeskMessagingAuthenticationFailed"; + ZendeskEvent["CLOSED"] = "zendeskMessagingClosed"; + ZendeskEvent["CONVERSATION_ADDED"] = "zendeskMessagingConversationAdded"; + ZendeskEvent["CONNECTION_STATUS_CHANGED"] = "zendeskMessagingConnectionStatusChanged"; + ZendeskEvent["OPENED"] = "zendeskMessagingOpened"; + ZendeskEvent["RECEIVED_MESSAGE"] = "zendeskMessagingReceivedMessage"; + ZendeskEvent["SEND_MESSAGE_FAILED"] = "zendeskMessagingSendMessageFailed"; + ZendeskEvent["UNREAD_COUNT_CHANGED"] = "zendeskMessagingUnreadCountChanged"; + return ZendeskEvent; +}({}); +export function initializeSDK(channelKey) { + return ReactNativeZendesk.initializeSDK(channelKey); +} +export function logUserIn(JWT) { + return ReactNativeZendesk.logUserIn(JWT); +} +export function logUserOut() { + return ReactNativeZendesk.logUserOut(); +} +export function close() { + return ReactNativeZendesk.close(); +} +export function open(metadata = null) { + return ReactNativeZendesk.open(metadata); +} +export function addEventListener(event, callback) { + return _nativeEventEmitter.addListener(event, callback); +} +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/lib/module/index.js.map b/lib/module/index.js.map new file mode 100644 index 0000000..4715dbb --- /dev/null +++ b/lib/module/index.js.map @@ -0,0 +1 @@ +{"version":3,"names":["NativeEventEmitter","NativeModules","Platform","LINKING_ERROR","select","ios","default","ReactNativeZendesk","Proxy","get","Error","_nativeEventEmitter","ZendeskEvent","initializeSDK","channelKey","logUserIn","JWT","logUserOut","close","open","metadata","addEventListener","event","callback","addListener"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":"AAAA,SAASA,kBAAkB,EAAEC,aAAa,EAAEC,QAAQ,QAAkC,cAAc;AAEpG,MAAMC,aAAa,GACjB,wFAAwF,GACxFD,QAAQ,CAACE,MAAM,CAAC;EAAEC,GAAG,EAAE,gCAAgC;EAAEC,OAAO,EAAE;AAAG,CAAC,CAAC,GACvE,sDAAsD;AAExD,MAAMC,kBAAkB,GAAGN,aAAa,CAACM,kBAAkB,GACvDN,aAAa,CAACM,kBAAkB,GAChC,IAAIC,KAAK,CACT,CAAC,CAAC,EACF;EACEC,GAAGA,CAAA,EAAG;IACJ,MAAM,IAAIC,KAAK,CAACP,aAAa,CAAC;EAChC;AACF,CACF,CAAC;AAEH,MAAMQ,mBAAmB,GAAG,IAAIX,kBAAkB,CAACO,kBAAkB,CAAC;AAmBtE,WAAYK,YAAY,0BAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAZA,YAAY;EAAA,OAAZA,YAAY;AAAA;AAgBxB,OAAO,SAASC,aAAaA,CAACC,UAAkB,EAAoB;EAClE,OAAOP,kBAAkB,CAACM,aAAa,CAACC,UAAU,CAAC;AACrD;AAEA,OAAO,SAASC,SAASA,CAACC,GAAW,EAAwB;EAC3D,OAAOT,kBAAkB,CAACQ,SAAS,CAACC,GAAG,CAAC;AAC1C;AAEA,OAAO,SAASC,UAAUA,CAAA,EAAqB;EAC7C,OAAOV,kBAAkB,CAACU,UAAU,CAAC,CAAC;AACxC;AAEA,OAAO,SAASC,KAAKA,CAAA,EAAqB;EACxC,OAAOX,kBAAkB,CAACW,KAAK,CAAC,CAAC;AACnC;AAEA,OAAO,SAASC,IAAIA,CAClBC,QAAyB,GAAG,IAAI,EACd;EAClB,OAAOb,kBAAkB,CAACY,IAAI,CAACC,QAAQ,CAAC;AAC1C;AAUA,OAAO,SAASC,gBAAgBA,CAC9BC,KAQmC,EACnCC,QAG0B,EACL;EACrB,OAAOZ,mBAAmB,CAACa,WAAW,CAACF,KAAK,EAAEC,QAAQ,CAAC;AACzD","ignoreList":[]} diff --git a/lib/typescript/src/index.d.ts b/lib/typescript/src/index.d.ts new file mode 100644 index 0000000..e3924b0 --- /dev/null +++ b/lib/typescript/src/index.d.ts @@ -0,0 +1,44 @@ +import { type EmitterSubscription } from 'react-native'; +type ConversationField = { + id: string; + value: number | string; +}; +export type Metadata = { + tags?: string[]; + fields?: ConversationField[]; +}; +export type ChatOptions = { + enableAgentAvailability?: Boolean; + enablePreChatForm?: Boolean; + enableTranscript?: Boolean; + enableOfflineForm?: Boolean; +}; +export declare enum ZendeskEvent { + AUTHENTICATION_FAILED = "zendeskMessagingAuthenticationFailed", + CLOSED = "zendeskMessagingClosed", + CONVERSATION_ADDED = "zendeskMessagingConversationAdded", + CONNECTION_STATUS_CHANGED = "zendeskMessagingConnectionStatusChanged", + OPENED = "zendeskMessagingOpened", + RECEIVED_MESSAGE = "zendeskMessagingReceivedMessage", + SEND_MESSAGE_FAILED = "zendeskMessagingSendMessageFailed", + UNREAD_COUNT_CHANGED = "zendeskMessagingUnreadCountChanged" +} +export type ZendeskUser = { + id: string; + externalId: string; +}; +export declare function initializeSDK(channelKey: String): Promise; +export declare function logUserIn(JWT: string): Promise; +export declare function logUserOut(): Promise; +export declare function close(): Promise; +export declare function open(metadata?: Metadata | null): Promise; +export declare function addEventListener(event: ZendeskEvent.AUTHENTICATION_FAILED, callback: (error: string) => void): EmitterSubscription; +export declare function addEventListener(event: ZendeskEvent.CLOSED, callback: () => void): EmitterSubscription; +export declare function addEventListener(event: ZendeskEvent.OPENED, callback: () => void): EmitterSubscription; +export declare function addEventListener(event: ZendeskEvent.CONVERSATION_ADDED, callback: (id: string) => void): EmitterSubscription; +export declare function addEventListener(event: ZendeskEvent.CONNECTION_STATUS_CHANGED, callback: (status: string) => void): EmitterSubscription; +export declare function addEventListener(event: ZendeskEvent.RECEIVED_MESSAGE, callback: (data: Record) => void): EmitterSubscription; +export declare function addEventListener(event: ZendeskEvent.SEND_MESSAGE_FAILED, callback: (error: string) => void): EmitterSubscription; +export declare function addEventListener(event: ZendeskEvent.UNREAD_COUNT_CHANGED, callback: (count: number) => void): EmitterSubscription; +export {}; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/lib/typescript/src/index.d.ts.map b/lib/typescript/src/index.d.ts.map new file mode 100644 index 0000000..935842b --- /dev/null +++ b/lib/typescript/src/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAA+C,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAoBrG,KAAK,iBAAiB,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,iBAAiB,EAAE,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,CAAC;AAEF,oBAAY,YAAY;IACtB,qBAAqB,yCAAyC;IAC9D,MAAM,2BAA2B;IACjC,kBAAkB,sCAAsC;IACxD,yBAAyB,4CAA4C;IACrE,MAAM,2BAA2B;IACjC,gBAAgB,oCAAoC;IACpD,mBAAmB,sCAAsC;IACzD,oBAAoB,uCAAuC;CAC5D;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAElE;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAE3D;AAED,wBAAgB,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC,CAE7C;AAED,wBAAgB,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC,CAExC;AAED,wBAAgB,IAAI,CAClB,QAAQ,GAAE,QAAQ,GAAG,IAAW,GAC/B,OAAO,CAAC,OAAO,CAAC,CAElB;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,CAAC,qBAAqB,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,mBAAmB,CAAC;AACpI,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,mBAAmB,CAAC;AACxG,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,mBAAmB,CAAC;AACxG,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,CAAC,kBAAkB,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,GAAG,mBAAmB,CAAC;AAC9H,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,CAAC,yBAAyB,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,mBAAmB,CAAC;AACzI,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,CAAC,gBAAgB,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,mBAAmB,CAAC;AAC/I,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,CAAC,mBAAmB,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,mBAAmB,CAAC;AAClI,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,CAAC,oBAAoB,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,mBAAmB,CAAC"} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 7f6b097..2865cf5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ -import { NativeEventEmitter, NativeModules, Platform } from 'react-native'; +import { NativeEventEmitter, NativeModules, Platform, type EmitterSubscription } from 'react-native'; const LINKING_ERROR = - `The package 'react-native-zendesk' doesn't seem to be linked. Make sure: \n\n` + + `The package '@wavyapp/react-native-zendesk' doesn't seem to be linked. Make sure: \n\n` + Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) + '- You rebuilt the app after installing the package\n'; @@ -18,22 +18,14 @@ const ReactNativeZendesk = NativeModules.ReactNativeZendesk const _nativeEventEmitter = new NativeEventEmitter(ReactNativeZendesk); -export type InitOptions = { - zendeskUrl: string; - appId: string; - clientId: string; - chatAppId?: string; - chatAccountKey?: string; +type ConversationField = { + id: string; + value: number | string; }; -export type UserIdentityTraits = { - name?: string; - email?: string; -}; - -export type ChatVisitorInfo = UserIdentityTraits & { - phone?: string; +export type Metadata = { tags?: string[]; + fields?: ConversationField[]; }; export type ChatOptions = { @@ -44,24 +36,65 @@ export type ChatOptions = { }; export enum ZendeskEvent { - RECEIVED_MESSAGE = "zendeskChatReceivedMessage" + AUTHENTICATION_FAILED = "zendeskMessagingAuthenticationFailed", + CLOSED = "zendeskMessagingClosed", + CONVERSATION_ADDED = "zendeskMessagingConversationAdded", + CONNECTION_STATUS_CHANGED = "zendeskMessagingConnectionStatusChanged", + OPENED = "zendeskMessagingOpened", + RECEIVED_MESSAGE = "zendeskMessagingReceivedMessage", + SEND_MESSAGE_FAILED = "zendeskMessagingSendMessageFailed", + UNREAD_COUNT_CHANGED = "zendeskMessagingUnreadCountChanged", +} + +export type ZendeskUser = { + id: string; + externalId: string; +}; + +export function initializeSDK(channelKey: String): Promise { + return ReactNativeZendesk.initializeSDK(channelKey); +} + +export function logUserIn(JWT: string): Promise { + return ReactNativeZendesk.logUserIn(JWT); } -export function initialize(opts: InitOptions): Promise { - return ReactNativeZendesk.initialize(opts.zendeskUrl, opts.appId, opts.clientId, opts.chatAppId || null, opts.chatAccountKey || null); +export function logUserOut(): Promise { + return ReactNativeZendesk.logUserOut(); } -export function identifyUser(traits: UserIdentityTraits): Promise { - return ReactNativeZendesk.identifyUser(traits); +export function close(): Promise { + return ReactNativeZendesk.close(); } -export function openChat( - visitorInfo: ChatVisitorInfo | null = null, - chatOpts: ChatOptions | null = null, +export function open( + metadata: Metadata | null = null, ): Promise { - return ReactNativeZendesk.openChat(visitorInfo, chatOpts); + return ReactNativeZendesk.open(metadata); } -export function addEventListener(event: ZendeskEvent, callback: (data?: Record) => void) { +export function addEventListener(event: ZendeskEvent.AUTHENTICATION_FAILED, callback: (error: string) => void): EmitterSubscription; +export function addEventListener(event: ZendeskEvent.CLOSED, callback: () => void): EmitterSubscription; +export function addEventListener(event: ZendeskEvent.OPENED, callback: () => void): EmitterSubscription; +export function addEventListener(event: ZendeskEvent.CONVERSATION_ADDED, callback: (id: string) => void): EmitterSubscription; +export function addEventListener(event: ZendeskEvent.CONNECTION_STATUS_CHANGED, callback: (status: string) => void): EmitterSubscription; +export function addEventListener(event: ZendeskEvent.RECEIVED_MESSAGE, callback: (data: Record) => void): EmitterSubscription; +export function addEventListener(event: ZendeskEvent.SEND_MESSAGE_FAILED, callback: (error: string) => void): EmitterSubscription; +export function addEventListener(event: ZendeskEvent.UNREAD_COUNT_CHANGED, callback: (count: number) => void): EmitterSubscription; +export function addEventListener( + event: + ZendeskEvent.AUTHENTICATION_FAILED | + ZendeskEvent.CLOSED | + ZendeskEvent.CONVERSATION_ADDED | + ZendeskEvent.CONNECTION_STATUS_CHANGED | + ZendeskEvent.OPENED | + ZendeskEvent.RECEIVED_MESSAGE | + ZendeskEvent.SEND_MESSAGE_FAILED | + ZendeskEvent.UNREAD_COUNT_CHANGED, + callback: + ((data: Record) => void) | + ((data: string) => void) | + ((data: number) => void) +): EmitterSubscription { return _nativeEventEmitter.addListener(event, callback) } \ No newline at end of file