infobip-mobile-messaging-react-native-plugin 13.8.1 → 13.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -87,7 +87,7 @@ repositories {
87
87
  }
88
88
 
89
89
  dependencies {
90
- def mmVersion = '14.1.3'
90
+ def mmVersion = '14.6.0'
91
91
  //react and mm dependencies clash
92
92
  if (!overrideKotlinVersion.empty) {
93
93
  constraints {
@@ -28,6 +28,8 @@ import org.infobip.mobile.messaging.chat.view.styles.InAppChatInputViewStyle;
28
28
  import org.infobip.mobile.messaging.chat.view.styles.InAppChatStyle;
29
29
  import org.infobip.mobile.messaging.chat.view.styles.InAppChatTheme;
30
30
  import org.infobip.mobile.messaging.chat.view.styles.InAppChatToolbarStyle;
31
+ import org.infobip.mobile.messaging.chat.core.InAppChatException;
32
+ import org.infobip.mobile.messaging.chat.view.InAppChatErrorsHandler;
31
33
  import org.infobip.mobile.messaging.chat.core.widget.LivechatWidgetLanguage;
32
34
  import org.infobip.mobile.messaging.mobileapi.MobileMessagingError;
33
35
  import org.infobip.mobile.messaging.mobileapi.Result;
@@ -50,7 +52,9 @@ import java.net.URL;
50
52
 
51
53
  public class RNMMChatModule extends ReactContextBaseJavaModule implements ActivityEventListener, LifecycleEventListener {
52
54
 
53
- private static final String EVENT_INAPPCHAT_JWT_REQUESTED = "inAppChat.jwtRequested";
55
+ private static final String EVENT_INAPPCHAT_JWT_REQUESTED = "inAppChat.internal.jwtRequested";
56
+ private static final String EVENT_INAPPCHAT_EXCEPTION_RECEIVED = "inAppChat.internal.exceptionReceived";
57
+
54
58
  private static ReactApplicationContext reactContext;
55
59
  private final ChatJwtCallbackHolder chatJwtCallbackHolder = new ChatJwtCallbackHolder();
56
60
 
@@ -72,11 +76,6 @@ public class RNMMChatModule extends ReactContextBaseJavaModule implements Activi
72
76
  InAppChat.getInstance(reactContext).inAppChatScreen().show();
73
77
  }
74
78
 
75
- @ReactMethod
76
- public void showThreadsList() {
77
- InAppChat.getInstance(reactContext).showThreadsList();
78
- }
79
-
80
79
  @ReactMethod
81
80
  public void getMessageCounter(final Callback successCallback) {
82
81
  successCallback.invoke(InAppChat.getInstance(reactContext).getMessageCounter());
@@ -221,6 +220,44 @@ public class RNMMChatModule extends ReactContextBaseJavaModule implements Activi
221
220
  }
222
221
  }
223
222
 
223
+ @ReactMethod
224
+ public void setChatExceptionHandler(boolean isHandlerPresent) {
225
+ if (isHandlerPresent) {
226
+ InAppChat.getInstance(reactContext).inAppChatScreen().setErrorHandler(createErrorsHandler());
227
+ } else {
228
+ InAppChat.getInstance(reactContext).inAppChatScreen().setErrorHandler(null);
229
+ }
230
+ }
231
+
232
+ private InAppChatErrorsHandler createErrorsHandler() {
233
+ return new InAppChatErrorsHandler() {
234
+ @Override
235
+ public void handlerError(@NonNull String error) {
236
+ // Deprecated method
237
+ }
238
+
239
+ @Override
240
+ public void handlerWidgetError(@NonNull String error) {
241
+ // Deprecated method
242
+ }
243
+
244
+ @Override
245
+ public void handlerNoInternetConnectionError(boolean hasConnection) {
246
+ // Deprecated method
247
+ }
248
+
249
+ @Override
250
+ public boolean handleError(@NonNull InAppChatException exception) {
251
+ if (reactContext != null) {
252
+ ReactNativeEvent.send(EVENT_INAPPCHAT_EXCEPTION_RECEIVED, reactContext, exception.toJSON());
253
+ } else {
254
+ Log.e(Utils.TAG, "React context is null, cannot propagate chat exception.");
255
+ }
256
+ return true;
257
+ }
258
+ };
259
+ }
260
+
224
261
  @ReactMethod
225
262
  public void setWidgetTheme(String widgetTheme) {
226
263
  if (widgetTheme != null) {
@@ -15,6 +15,8 @@ import com.facebook.react.uimanager.annotations.ReactProp;
15
15
  class RNMMChatViewManager extends ViewGroupManager<ReactChatView> {
16
16
  public static final String COMMAND_ADD = "add";
17
17
  public static final String COMMAND_REMOVE = "remove";
18
+ public static final String COMMAND_SHOW_THREADS_LIST = "showThreadsList";
19
+ public static final String COMMAND_SET_EXCEPTION_HANDLER = "setExceptionHandler";
18
20
 
19
21
  public static final String VIEW_GROUP_MANAGER_NAME = "RNMMChatView";
20
22
  private ReactApplicationContext context;
@@ -42,20 +44,19 @@ class RNMMChatViewManager extends ViewGroupManager<ReactChatView> {
42
44
  @Override
43
45
  public void receiveCommand(@NonNull ReactChatView root, String commandId, @Nullable ReadableArray args) {
44
46
  super.receiveCommand(root, commandId, args);
45
-
46
- if (args == null) {
47
- Log.e(Utils.TAG, "RNMMChatViewManager received command without argumnents, Id: " + commandId);
48
- return;
49
- }
50
- int reactNativeViewId = args.getInt(0);
51
-
52
47
  switch (commandId) {
53
48
  case COMMAND_ADD:
54
- addChatFragment(root, reactNativeViewId);
49
+ addChatFragment(root);
55
50
  break;
56
51
  case COMMAND_REMOVE:
57
52
  removeChatFragment(root);
58
53
  break;
54
+ case COMMAND_SHOW_THREADS_LIST:
55
+ showThreadsList(root);
56
+ break;
57
+ case COMMAND_SET_EXCEPTION_HANDLER:
58
+ setExceptionHandler(root, args.getBoolean(0));
59
+ break;
59
60
  default: {
60
61
  Log.w(Utils.TAG, "RNMMChatViewManager received unsupported command with Id: " + commandId);
61
62
  }
@@ -72,15 +73,27 @@ class RNMMChatViewManager extends ViewGroupManager<ReactChatView> {
72
73
  view.setSendButtonColor(hexSendButtonColor, Utils.getFragmentActivity(this.context));
73
74
  }
74
75
 
75
- private void addChatFragment(FrameLayout parentLayout, int reactNativeViewId) {
76
+ private void addChatFragment(ReactChatView chatView) {
77
+ if (chatView != null) {
78
+ chatView.addChatFragment(this.context, Utils.getFragmentActivity(this.context));
79
+ }
80
+ }
81
+
82
+ private void removeChatFragment(ReactChatView chatView) {
83
+ if (chatView != null) {
84
+ chatView.removeChatFragment(Utils.getFragmentActivity(this.context));
85
+ }
86
+ }
87
+
88
+ private void showThreadsList(ReactChatView chatView) {
76
89
  if (chatView != null) {
77
- chatView.addChatFragment(parentLayout, reactNativeViewId, Utils.getFragmentActivity(this.context));
90
+ chatView.showThreadsList(Utils.getFragmentActivity(this.context));
78
91
  }
79
92
  }
80
93
 
81
- private void removeChatFragment(FrameLayout parentLayout) {
94
+ private void setExceptionHandler(ReactChatView chatView, boolean isHandlerPresent) {
82
95
  if (chatView != null) {
83
- chatView.removeChatFragment(parentLayout, Utils.getFragmentActivity(this.context));
96
+ chatView.setExceptionHandler(isHandlerPresent, this.context, Utils.getFragmentActivity(this.context));
84
97
  }
85
98
  }
86
99
  }
@@ -5,21 +5,32 @@ import android.os.Build;
5
5
  import android.util.AttributeSet;
6
6
  import android.util.Log;
7
7
  import android.view.ViewGroup;
8
+ import android.view.ViewParent;
8
9
  import android.widget.FrameLayout;
10
+ import android.view.ViewTreeObserver;
9
11
 
10
12
  import androidx.annotation.NonNull;
11
13
  import androidx.annotation.Nullable;
12
14
  import androidx.annotation.RequiresApi;
13
15
  import androidx.fragment.app.Fragment;
16
+ import androidx.fragment.app.FragmentContainerView;
14
17
  import androidx.fragment.app.FragmentActivity;
15
18
  import androidx.fragment.app.FragmentManager;
16
19
  import androidx.fragment.app.FragmentTransaction;
17
20
 
18
21
  import org.infobip.mobile.messaging.chat.InAppChat;
19
22
  import org.infobip.mobile.messaging.chat.view.InAppChatFragment;
23
+ import org.infobip.mobile.messaging.chat.core.InAppChatException;
24
+ import org.infobip.mobile.messaging.chat.view.InAppChatErrorsHandler;
25
+
26
+ import com.facebook.react.bridge.ReactApplicationContext;
20
27
 
21
28
  class ReactChatView extends FrameLayout {
22
29
 
30
+ private InAppChatFragment fragment;
31
+ private boolean useCustomErrorHandler = false;
32
+ private static final String EVENT_INAPPCHAT_EXCEPTION_RECEIVED = "inAppChat.internal.exceptionReceived";
33
+
23
34
  public ReactChatView(@NonNull Context context) {
24
35
  super(context);
25
36
  }
@@ -37,56 +48,131 @@ class ReactChatView extends FrameLayout {
37
48
  super(context, attrs, defStyleAttr, defStyleRes);
38
49
  }
39
50
 
40
- @Override
41
- public void requestLayout() {
42
- super.requestLayout();
43
-
44
- //RN issue https://github.com/facebook/react-native/issues/17968
45
- //Without this layout will not be called and view will not be displayed, because RN doesn't dispatches events to android views properly
46
- post(measureAndLayout);
47
- }
48
-
49
51
  public void setSendButtonColor(String hexColorString, @Nullable FragmentActivity fragmentActivity) {
50
52
  Log.d(Utils.TAG, "Not implemented for Android, use IB_AppTheme.Chat theme instead");
51
53
  }
52
54
 
53
- public void addChatFragment(FrameLayout parentLayout, int reactNativeViewId, @Nullable FragmentActivity fragmentActivity) {
54
- if (fragmentActivity == null) {
55
- return;
55
+ private InAppChatFragment getFragment(FragmentActivity fragmentActivity) {
56
+ if (this.fragment != null) {
57
+ return this.fragment;
58
+ }
59
+ if (fragmentActivity != null) {
60
+ FragmentManager fragmentManager = fragmentActivity.getSupportFragmentManager();
61
+ if (fragmentManager != null) {
62
+ Fragment fragment = fragmentManager.findFragmentByTag(Utils.RN_IN_APP_CHAT_FRAGMENT_TAG);
63
+ if (fragment instanceof InAppChatFragment) {
64
+ return (InAppChatFragment) fragment;
65
+ }
66
+ } else {
67
+ Log.e(Utils.TAG, "FragmentManager is null, cannot get InAppChatFragment.");
68
+ }
69
+ } else {
70
+ Log.e(Utils.TAG, "FragmentActivity is null, cannot get InAppChatFragment.");
56
71
  }
72
+ return null;
73
+ }
57
74
 
58
- //RN issue https://github.com/facebook/react-native/issues/17968
59
- //Without this layout will not be called and view will not be displayed, because RN doesn't dispatches events to android views properly
60
- ViewGroup parentView = (ViewGroup) parentLayout.findViewById(reactNativeViewId).getParent();
61
- setupLayoutHack(parentView);
75
+ public void addChatFragment(@Nullable ReactApplicationContext reactContext, @Nullable FragmentActivity fragmentActivity) {
76
+ fragment = getFragment(fragmentActivity);
77
+ if (fragment == null) {
78
+ fragment = new InAppChatFragment();
79
+ }
80
+ fragment.setWithToolbar(false);
81
+ fragment.setWithInput(true);
82
+ if (useCustomErrorHandler && reactContext != null) {
83
+ fragment.setErrorsHandler(createErrorsHandler(reactContext));
84
+ }
62
85
 
86
+ ViewParent parent = this.getParent();
87
+ if (parent instanceof ViewGroup) {
88
+ setupLayoutHack((ViewGroup) parent);
89
+ } else {
90
+ Log.e(Utils.TAG, "Parent is not ViewGroup, cannot show InAppChatFragment.");
91
+ }
63
92
  FragmentManager fragmentManager = fragmentActivity.getSupportFragmentManager();
64
- InAppChat.getInstance(fragmentActivity.getApplicationContext()).showInAppChatFragment(fragmentManager, reactNativeViewId);
65
- fragmentManager.executePendingTransactions();
66
- Fragment inAppChatFragment = fragmentManager.findFragmentByTag(Utils.RN_IN_APP_CHAT_FRAGMENT_TAG);
67
- Log.e(Utils.TAG, "InAppChatFragment found " + (inAppChatFragment != null));
68
- if (inAppChatFragment instanceof InAppChatFragment) {
69
- ((InAppChatFragment) inAppChatFragment).setWithToolbar(false);
93
+ if (fragmentManager != null) {
94
+ fragmentManager.beginTransaction()
95
+ .replace(this.getId(), fragment, Utils.RN_IN_APP_CHAT_FRAGMENT_TAG)
96
+ .commitNow();
97
+ } else {
98
+ Log.e(Utils.TAG, "FragmentManager is null, cannot add InAppChatFragment.");
70
99
  }
71
100
  }
72
101
 
73
- public void removeChatFragment(FrameLayout parentLayout, @Nullable FragmentActivity fragmentActivity) {
74
- if (fragmentActivity == null) {
75
- return;
76
- }
102
+ public void removeChatFragment(@Nullable FragmentActivity fragmentActivity) {
103
+ useCustomErrorHandler = false;
77
104
  FragmentManager fragmentManager = fragmentActivity.getSupportFragmentManager();
78
- FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
79
- Fragment fragment = fragmentManager.findFragmentByTag(Utils.RN_IN_APP_CHAT_FRAGMENT_TAG);
105
+ Fragment fragment = getFragment(fragmentActivity);
106
+ if (fragment != null && fragmentManager != null) {
107
+ fragmentManager.beginTransaction()
108
+ .remove(fragment)
109
+ .commitNow();
110
+ } else {
111
+ Log.e(Utils.TAG, "InAppChatFragment or FragmentManager is null, cannot remove InAppChatFragment.");
112
+ }
113
+ }
114
+
115
+ public void showThreadsList(@Nullable FragmentActivity fragmentActivity) {
116
+ InAppChatFragment fragment = getFragment(fragmentActivity);
80
117
  if (fragment != null) {
81
- fragmentTransaction.remove(fragment);
118
+ fragment.showThreadList();
119
+ } else {
120
+ Log.e(Utils.TAG, "InAppChatFragment is null, cannot show threads list.");
121
+ }
122
+ }
123
+
124
+ public void setExceptionHandler(boolean isHandlerPresent, @Nullable ReactApplicationContext reactContext, @Nullable FragmentActivity fragmentActivity) {
125
+ //If exception handler is set before adding the fragment, it will be applied in addChatFragment()
126
+ useCustomErrorHandler = isHandlerPresent;
127
+ InAppChatFragment fragment = getFragment(fragmentActivity);
128
+ if (fragment != null && reactContext != null) {
129
+ if (isHandlerPresent) {
130
+ fragment.setErrorsHandler(createErrorsHandler(reactContext));
131
+ } else {
132
+ fragment.setErrorsHandler(fragment.getDefaultErrorsHandler());
133
+ }
82
134
  }
83
- fragmentTransaction.commit();
135
+ }
136
+
137
+ private InAppChatFragment.ErrorsHandler createErrorsHandler(ReactApplicationContext reactContext) {
138
+ return new InAppChatFragment.ErrorsHandler() {
139
+ @Override
140
+ public void handlerError(@NonNull String error) {
141
+ // Deprecated method
142
+ }
143
+
144
+ @Override
145
+ public void handlerWidgetError(@NonNull String error) {
146
+ // Deprecated method
147
+ }
148
+
149
+ @Override
150
+ public void handlerNoInternetConnectionError(boolean hasConnection) {
151
+ // Deprecated method
152
+ }
153
+
154
+ @Override
155
+ public boolean handleError(@NonNull InAppChatException exception) {
156
+ ReactNativeEvent.send(EVENT_INAPPCHAT_EXCEPTION_RECEIVED, reactContext, exception.toJSON());
157
+ return true;
158
+ }
159
+ };
84
160
  }
85
161
 
86
162
  private void setupLayoutHack(final ViewGroup view) {
87
163
  view.getViewTreeObserver().addOnGlobalLayoutListener(view::requestLayout);
88
164
  }
89
165
 
166
+ @Override
167
+ public void requestLayout() {
168
+ super.requestLayout();
169
+
170
+ // RN issue https://github.com/facebook/react-native/issues/17968
171
+ // Without this layout will not be called and view will not be displayed,
172
+ // because RN doesn't dispatches events to android views properly
173
+ post(measureAndLayout);
174
+ }
175
+
90
176
  private final Runnable measureAndLayout = () -> {
91
177
  measure(
92
178
  MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
@@ -1,7 +1,7 @@
1
1
  require "json"
2
2
 
3
3
  package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
- mmVersion = "13.11.2"
4
+ mmVersion = "13.12.0"
5
5
 
6
6
  Pod::Spec.new do |s|
7
7
  s.name = "infobip-mobile-messaging-react-native-plugin"
@@ -11,6 +11,7 @@ import MobileMessaging
11
11
  @objc(RNMMChat)
12
12
  class RNMMChat: NSObject {
13
13
  private var willUseJWT = false
14
+ private var willUseChatExceptionHandler = false
14
15
  private var jwtRequestQueue: [((String?) -> Void)] = []
15
16
  private let jwtQueueLock = NSLock()
16
17
 
@@ -143,6 +144,12 @@ class RNMMChat: NSObject {
143
144
  willUseJWT = true
144
145
  }
145
146
 
147
+ @objc(setChatExceptionHandler:)
148
+ func setChatExceptionHandler(isHandlerPresent: NSNumber) {
149
+ // NSNumber due to how RN bridge wraps JavaScript booleans
150
+ willUseChatExceptionHandler = isHandlerPresent.boolValue
151
+ }
152
+
146
153
  @objc(setChatJwt:)
147
154
  func setChatJwt(jwt: String?) {
148
155
  jwtQueueLock.lock()
@@ -181,4 +188,22 @@ extension RNMMChat: MMInAppChatDelegate {
181
188
  _ = semaphore.wait(timeout: .now() + 45)
182
189
  return jwtResult
183
190
  }
191
+
192
+ @objc public func didReceiveException(_ exception: MMChatException) -> MMChatExceptionDisplayMode {
193
+ guard willUseChatExceptionHandler else { return .displayDefaultAlert }
194
+
195
+ var payload: [String: Any] = [:]
196
+ if let message = exception.message {
197
+ payload["message"] = message
198
+ }
199
+ if let name = exception.name {
200
+ payload["name"] = name
201
+ }
202
+ payload["code"] = exception.code
203
+ payload["origin"] = "LiveChat"
204
+ payload["platform"] = "Flutter"
205
+
206
+ ReactNativeMobileMessaging.shared?.sendEvent(withName: EventName.inAppChat_exceptionReceived, body: payload)
207
+ return .noDisplay
208
+ }
184
209
  }
@@ -23,6 +23,7 @@ RCT_EXTERN_METHOD(setChatCustomization:)
23
23
  RCT_EXTERN_METHOD(setLanguage:(NSString *)data onSuccess:(RCTResponseSenderBlock)successCallback onError:(RCTResponseSenderBlock)errorCallback)
24
24
  RCT_EXTERN_METHOD(setChatJwt:)
25
25
  RCT_EXTERN_METHOD(setChatJwtProvider)
26
+ RCT_EXTERN_METHOD(setChatExceptionHandler:)
26
27
  RCT_EXTERN_METHOD(sendContextualData:(NSString *)data chatMultiThreadStrategy:(NSString *)chatMultiThreadStrategy onSuccess:(RCTResponseSenderBlock)successCallback onError:(RCTResponseSenderBlock)errorCallback)
27
28
  RCT_EXTERN_METHOD(showThreadsList)
28
29
  RCT_EXTERN_METHOD(restartConnection)
@@ -41,7 +41,8 @@ class ReactNativeMobileMessaging: RCTEventEmitter {
41
41
  EventName.inAppChat_viewStateChanged,
42
42
  EventName.inAppChat_configurationSynced,
43
43
  EventName.inAppChat_registrationIdUpdated,
44
- EventName.inAppChat_jwtRequested
44
+ EventName.inAppChat_jwtRequested,
45
+ EventName.inAppChat_exceptionReceived
45
46
  ]
46
47
  }
47
48
 
@@ -107,7 +107,8 @@ struct EventName {
107
107
  static let inAppChat_viewStateChanged = "inAppChat.viewStateChanged"
108
108
  static let inAppChat_configurationSynced = "inAppChat.configurationSynced"
109
109
  static let inAppChat_registrationIdUpdated = "inAppChat.livechatRegistrationIdUpdated"
110
- static let inAppChat_jwtRequested = "inAppChat.jwtRequested"
110
+ static let inAppChat_jwtRequested = "inAppChat.internal.jwtRequested"
111
+ static let inAppChat_exceptionReceived = "inAppChat.internal.exceptionReceived"
111
112
  }
112
113
 
113
114
  extension UIApplication {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "infobip-mobile-messaging-react-native-plugin",
3
3
  "title": "Infobip Mobile Messaging React Native Plugin",
4
- "version": "13.8.1",
4
+ "version": "13.10.0",
5
5
  "description": "Infobip Mobile Messaging React Native Plugin",
6
6
  "main": "./src/index.js",
7
7
  "scripts": {
@@ -1,51 +1,112 @@
1
1
  import PropTypes from 'prop-types';
2
- import React, { FC, useLayoutEffect, useRef } from "react";
2
+ import React, { useLayoutEffect, useRef, useImperativeHandle, forwardRef } from "react";
3
3
  import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';
4
- import {findNodeHandle, requireNativeComponent} from 'react-native'
5
-
6
- interface RNMMChatViewProps {
7
- sendButtonColor: ?string
8
- }
4
+ import { requireNativeComponent, Platform, NativeEventEmitter, NativeModules } from 'react-native'
5
+ import { mobileMessaging } from 'infobip-mobile-messaging-react-native-plugin';
9
6
 
10
7
  const RNMMChatViewCommands = codegenNativeCommands({
11
- supportedCommands: ["add", "remove"],
8
+ supportedCommands: ["add", "remove", "showThreadsList", "setExceptionHandler"],
12
9
  });
13
10
 
14
- export const ChatView: FC<RNMMChatViewProps> = props => {
15
- const ref = useRef(null);
11
+
12
+ export const ChatView = forwardRef((props, ref) => {
13
+ const CHAT_EVENT_EXCEPTION_RECEIVED = 'inAppChat.internal.exceptionReceived';
14
+ const innerRef = useRef(null);
15
+ let eventEmitter = new NativeEventEmitter(NativeModules.ReactNativeMobileMessaging);
16
+
17
+ useImperativeHandle(ref, () => ({
18
+
19
+ /**
20
+ * Navigates to THREAD_LIST view in multithread widget.
21
+ * @name showThreadsList
22
+ */
23
+ showThreadsList: () => {
24
+ if (Platform.OS === "ios") {
25
+ mobileMessaging.showThreadsList();
26
+ return;
27
+ }
28
+ // Nothing to do if there is no chatView reference.
29
+ let chatViewRef = innerRef.current;
30
+ if (!chatViewRef) return;
31
+ RNMMChatViewCommands.showThreadsList(chatViewRef);
32
+ },
33
+
34
+ /**
35
+ * Sets the chat exception handler in case you want to intercept and
36
+ * display the errors coming from the chat on your own (instead of relying on the prebuild error banners).
37
+ *
38
+ * The `exceptionHandler` is a function that receives the exception. Passing `null` will remove the previously set handler.
39
+ *
40
+ * ```ts
41
+ * chatViewRef.current?.setChatExceptionHandler((exception) => {
42
+ * console.log("ChatView exception occurred:", exception);
43
+ * });
44
+ * ```
45
+ *
46
+ * @param exceptionHandler A function that receives an ChatException when it happens. Passing `null` will remove the previously set handler.
47
+ * @param onError Optional error handler for catching exceptions thrown when listening for exceptions.
48
+ */
49
+ setExceptionHandler(exceptionHandler, onError) {
50
+ // Not needed for iOS.
51
+ if (Platform.OS === "ios") {
52
+ mobileMessaging.setChatExceptionHandler(
53
+ (exception) => console.log('ChatView exception received: ' + JSON.stringify(exception)),
54
+ (error) => console.log('ChatView exception handler error: ' + error)
55
+ );
56
+ return;
57
+ }
58
+
59
+ // Nothing to do if there is no chatView reference.
60
+ let chatViewRef = innerRef.current;
61
+ if (!chatViewRef) return;
62
+
63
+ eventEmitter.removeAllListeners(CHAT_EVENT_EXCEPTION_RECEIVED);
64
+ let isHandlerPresent = typeof exceptionHandler === 'function';
65
+ if (isHandlerPresent) {
66
+ const handleError = (e) => {
67
+ if (onError) {
68
+ onError(e);
69
+ } else {
70
+ console.error('[RNMobileMessaging] Could not handle chat exception:', e);
71
+ }
72
+ };
73
+
74
+ eventEmitter.addListener(CHAT_EVENT_EXCEPTION_RECEIVED, (chatException) => {
75
+ try {
76
+ exceptionHandler(chatException);
77
+ } catch (e) {
78
+ handleError(e);
79
+ }
80
+ });
81
+ }
82
+ RNMMChatViewCommands.setExceptionHandler(chatViewRef, isHandlerPresent);
83
+ }
84
+ }));
16
85
 
17
86
  useLayoutEffect(() => {
18
87
  // Not needed for iOS.
19
88
  if (Platform.OS === "ios") return;
20
89
 
21
- const chatViewRef = ref.current
22
90
  // Nothing to do if there is no chatView reference.
91
+ const chatViewRef = innerRef.current;
23
92
  if (!chatViewRef) return;
24
93
 
25
- let addedViewId: ?number = null
26
-
27
94
  // Fix for android, sometimes it can't get parent view, which is needed
28
95
  // for proper relayout.
29
96
  setTimeout(() => {
30
- const viewId = findNodeHandle(chatViewRef);
31
- if (viewId !== null && viewId !== undefined) {
32
- RNMMChatViewCommands.add(chatViewRef, viewId);
33
- console.log(`Adding android viewId: ${viewId}.`);
34
- addedViewId = viewId;
35
- }
97
+ console.log(`add command called`);
98
+ RNMMChatViewCommands.add(chatViewRef);
36
99
  }, 100);
37
100
 
38
101
  return () => {
39
- const viewId = addedViewId
40
- if (viewId !== null && viewId !== undefined) {
41
- console.log(`Removing android viewId: ${viewId}.`);
42
- RNMMChatViewCommands.remove(chatViewRef, viewId);
43
- }
102
+ console.log(`remove command called`);
103
+ eventEmitter.removeAllListeners(CHAT_EVENT_EXCEPTION_RECEIVED);
104
+ RNMMChatViewCommands.remove(chatViewRef);
44
105
  };
45
106
  }, []);
46
107
 
47
- return <RNMMChatView {...props} ref={ref} />;
48
- };
108
+ return <RNMMChatView {...props} ref={innerRef} />;
109
+ });
49
110
 
50
111
  ChatView.propTypes = {
51
112
  /**
package/src/index.d.ts CHANGED
@@ -265,6 +265,14 @@ declare namespace MobileMessagingReactNative {
265
265
  chatInputCursorColor: string;
266
266
  }
267
267
 
268
+ export interface ChatException {
269
+ code?: number;
270
+ name?: string;
271
+ message?: string;
272
+ origin?: string;
273
+ platform?: string;
274
+ }
275
+
268
276
  // To be deprecated in favour of ChatCustomization above
269
277
  export interface ChatCustomizationConfiguration {
270
278
  toolbarTitle: string;
@@ -635,6 +643,7 @@ declare namespace MobileMessagingReactNative {
635
643
  resetMessageCounter(): void;
636
644
 
637
645
  /**
646
+ * This method is iOS only and it has no effect in Android.
638
647
  * Navigates to THREAD_LIST view in multithread widget if in-app chat is shown as React Component.
639
648
  * @name showThreadsList
640
649
  */
@@ -670,6 +679,23 @@ declare namespace MobileMessagingReactNative {
670
679
  */
671
680
  setChatJwtProvider(jwtProvider: () => string | Promise<string>, onError?: (error: Error) => void): void;
672
681
 
682
+ /**
683
+ * Sets the chat exception handler in case you want to intercept and
684
+ * display the errors coming from the chat on your own (instead of relying on the prebuild error banners).
685
+ *
686
+ * The `exceptionHandler` is a function that receives the exception. Passing `null` will remove the previously set handler.
687
+ *
688
+ * ```ts
689
+ * mobileMessaging.setChatExceptionHandler((exception) => {
690
+ * console.log("Chat exception occurred:", exception);
691
+ * });
692
+ * ```
693
+ *
694
+ * @param exceptionHandler A function that receives an ChatException when it happens. Passing `null` will remove the previously set handler.
695
+ * @param onError Optional error handler for catching exceptions thrown when listening for exceptions.
696
+ */
697
+ setChatExceptionHandler(exceptionHandler?: (exception?: ChatException) => void, onError?: (error: Error) => void): void;
698
+
673
699
  /**
674
700
  * Registering for POST_NOTIFICATIONS permission for Android 13+
675
701
  */
package/src/index.js CHANGED
@@ -639,15 +639,19 @@ class MobileMessaging {
639
639
 
640
640
 
641
641
  /**
642
+ * This method is iOS only and it has no effect in Android.
642
643
  * Navigates to THREAD_LIST view in multithread widget if in-app chat is shown as React Component.
643
644
  * @name showThreadsList
644
645
  */
645
646
  showThreadsList() {
647
+ if (Platform.OS === "android") {
648
+ return;
649
+ }
646
650
  RNMMChat.showThreadsList();
647
651
  };
648
652
 
649
653
  /**
650
- * Private global variable holding reference to android jwt update events subscription.
654
+ * Private global variable holding reference to jwt update events subscription.
651
655
  */
652
656
  #jwtSubscription = null;
653
657
 
@@ -696,7 +700,7 @@ class MobileMessaging {
696
700
  }
697
701
  };
698
702
 
699
- this.#jwtSubscription = this.subscribe('inAppChat.jwtRequested', () => {
703
+ this.#jwtSubscription = this.subscribe('inAppChat.internal.jwtRequested', () => {
700
704
  try {
701
705
  const jwtPromise = jwtProvider();
702
706
  if (jwtPromise && typeof jwtPromise.then === 'function') { // Handle asynchronous JWT provider of type Promise<string>
@@ -713,6 +717,53 @@ class MobileMessaging {
713
717
  }
714
718
  }
715
719
 
720
+ /**
721
+ * Private global variable holding reference to chat exceptions subscription.
722
+ */
723
+ #chatExceptionHandlerSubscription = null;
724
+
725
+ /**
726
+ * Sets the chat exception handler in case you want to intercept and
727
+ * display the errors coming from the chat on your own (instead of relying on the prebuild error banners).
728
+ *
729
+ * The `exceptionHandler` is a function that receives the exception. Passing `null` will remove the previously set handler.
730
+ *
731
+ * ```ts
732
+ * mobileMessaging.setChatExceptionHandler((exception) => {
733
+ * console.log("Chat exception occurred:", exception);
734
+ * });
735
+ * ```
736
+ *
737
+ * @param exceptionHandler A function that receives an ChatException when it happens. Passing `null` will remove the previously set handler.
738
+ * @param onError Optional error handler for catching exceptions thrown when listening for exceptions.
739
+ */
740
+ setChatExceptionHandler(exceptionHandler, onError) {
741
+ if (this.#chatExceptionHandlerSubscription != null) {
742
+ this.unsubscribe(this.#chatExceptionHandlerSubscription);
743
+ this.#chatExceptionHandlerSubscription = null;
744
+ }
745
+
746
+ let isHandlerPresent = typeof exceptionHandler === 'function';
747
+ if (isHandlerPresent) {
748
+ const handleError = (e) => {
749
+ if (onError) {
750
+ onError(e);
751
+ } else {
752
+ console.error('[RNMobileMessaging] Could not handle chat exception:', e);
753
+ }
754
+ };
755
+
756
+ this.#chatExceptionHandlerSubscription = this.subscribe('inAppChat.internal.exceptionReceived', (chatException) => {
757
+ try {
758
+ exceptionHandler(chatException);
759
+ } catch (e) {
760
+ handleError(e);
761
+ }
762
+ });
763
+ }
764
+ RNMMChat.setChatExceptionHandler(isHandlerPresent);
765
+ }
766
+
716
767
  /**
717
768
  * Registering for POST_NOTIFICATIONS permission for Android 13+
718
769
  */