infobip-mobile-messaging-react-native-plugin 14.2.0-rc → 14.4.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.
Files changed (38) hide show
  1. package/android/build.gradle +2 -2
  2. package/android/src/main/java/org/infobip/reactlibrary/mobilemessaging/CacheManager.java +3 -3
  3. package/android/src/main/java/org/infobip/reactlibrary/mobilemessaging/Configuration.java +3 -63
  4. package/android/src/main/java/org/infobip/reactlibrary/mobilemessaging/PermissionsRequestManager.java +1 -2
  5. package/android/src/main/java/org/infobip/reactlibrary/mobilemessaging/RNMMChatService.kt +35 -33
  6. package/android/src/main/java/org/infobip/reactlibrary/mobilemessaging/RNMMChatView.kt +6 -6
  7. package/android/src/main/java/org/infobip/reactlibrary/mobilemessaging/RNMMLogWriter.kt +91 -0
  8. package/android/src/main/java/org/infobip/reactlibrary/mobilemessaging/RNMMLogger.kt +82 -0
  9. package/android/src/main/java/org/infobip/reactlibrary/mobilemessaging/RNMMWebRTCUIService.kt +61 -0
  10. package/android/src/main/java/org/infobip/reactlibrary/mobilemessaging/ReactNativeEvent.java +5 -5
  11. package/android/src/main/java/org/infobip/reactlibrary/mobilemessaging/ReactNativeMobileMessagingPackage.kt +4 -4
  12. package/android/src/main/java/org/infobip/reactlibrary/mobilemessaging/ReactNativeMobileMessagingService.kt +77 -64
  13. package/android/src/main/java/org/infobip/reactlibrary/mobilemessaging/Utils.java +1 -1
  14. package/android/src/newarchitecture/java/org/infobip/reactlibrary/mobilemessaging/MobileMessagingModule.kt +27 -27
  15. package/android/src/newarchitecture/java/org/infobip/reactlibrary/mobilemessaging/RNMMChatModule.kt +26 -16
  16. package/android/src/newarchitecture/java/org/infobip/reactlibrary/mobilemessaging/RNMMChatViewManager.kt +5 -5
  17. package/android/src/newarchitecture/java/org/infobip/reactlibrary/mobilemessaging/RNMMWebRTCUIModule.kt +3 -3
  18. package/android/src/oldarchitecture/java/org/infobip/reactlibrary/mobilemessaging/MobileMessagingModule.kt +27 -27
  19. package/android/src/oldarchitecture/java/org/infobip/reactlibrary/mobilemessaging/RNMMChatModule.kt +27 -15
  20. package/android/src/oldarchitecture/java/org/infobip/reactlibrary/mobilemessaging/RNMMChatViewManager.kt +6 -6
  21. package/android/src/oldarchitecture/java/org/infobip/reactlibrary/mobilemessaging/RNMMWebRTCUIModule.kt +3 -3
  22. package/infobip-mobile-messaging-react-native-plugin-14.3.0.tgz +0 -0
  23. package/infobip-mobile-messaging-react-native-plugin.podspec +1 -1
  24. package/ios/MobileMessagingPlugin/RNMMChat.swift +26 -3
  25. package/ios/MobileMessagingPlugin/RNMMChatBridge.m +2 -0
  26. package/ios/MobileMessagingPlugin/RNMMChatCustomization.swift +39 -102
  27. package/ios/MobileMessagingPlugin/RNMMLogger.swift +53 -0
  28. package/ios/MobileMessagingPlugin/RNMobileMessaging.swift +16 -7
  29. package/ios/MobileMessagingPlugin/RNMobileMessagingConfiguration.swift +4 -1
  30. package/ios/MobileMessagingPlugin/RNMobileMessagingEventsManager.swift +5 -3
  31. package/ios/MobileMessagingPlugin/RNMobileMessagingUtils.swift +1 -0
  32. package/ios/ReactNativeMobileMessaging.xcodeproj/project.pbxproj +4 -0
  33. package/package.json +4 -4
  34. package/release.sh +1 -1
  35. package/src/index.d.ts +160 -182
  36. package/src/index.js +46 -6
  37. package/src/specs/NativeRNMMChat.ts +2 -0
  38. package/infobip-mobile-messaging-react-native-plugin-14.1.4.tgz +0 -0
@@ -66,7 +66,7 @@ repositories {
66
66
  }
67
67
 
68
68
  dependencies {
69
- def mmVersion = '14.7.9'
69
+ def mmVersion = '14.11.0'
70
70
  implementation "org.jetbrains.kotlin:kotlin-stdlib:2.1.20"
71
71
  compileOnly "com.facebook.react:react-android"
72
72
  implementation "androidx.annotation:annotation:1.9.1"
@@ -100,4 +100,4 @@ dependencies {
100
100
  if (!overrideFirebaseVersion.empty) {
101
101
  implementation "com.google.firebase:firebase-messaging:$overrideFirebaseVersion"
102
102
  }
103
- }
103
+ }
@@ -50,7 +50,7 @@ class CacheManager {
50
50
 
51
51
  static void saveEvent(Context context, String event, JSONObject object, String actionId, String actionInputText) {
52
52
  if (context == null) {
53
- Log.e(Utils.TAG, "context is null, can't cache event " + event);
53
+ RNMMLogger.e(Utils.TAG, "context is null, can't cache event " + event);
54
54
  return;
55
55
  }
56
56
  String serialized = serializer.serialize(new Event(event, object, actionId, actionInputText));
@@ -59,7 +59,7 @@ class CacheManager {
59
59
 
60
60
  static void saveEvent(Context context, String event, int unreadMessagesCounter) {
61
61
  if (context == null) {
62
- Log.e(Utils.TAG, "context is null, can't cache event " + event);
62
+ RNMMLogger.e(Utils.TAG, "context is null, can't cache event " + event);
63
63
  return;
64
64
  }
65
65
  //int `unreadMessagesCounter` isn't a JSONObject, so it'll go as a second argument
@@ -69,7 +69,7 @@ class CacheManager {
69
69
 
70
70
  static Event[] loadEvents(Context context, String eventType) {
71
71
  if (context == null) {
72
- Log.e(Utils.TAG, "context is null, can't load cached events " + eventType);
72
+ RNMMLogger.e(Utils.TAG, "context is null, can't load cached events " + eventType);
73
73
  return new Event[0];
74
74
  }
75
75
  Set<String> serialized = getStringSet(context, EVENTS_KEY);
@@ -9,6 +9,7 @@
9
9
  package org.infobip.reactlibrary.mobilemessaging;
10
10
 
11
11
  import androidx.annotation.NonNull;
12
+ import androidx.annotation.Nullable;
12
13
 
13
14
  import com.google.firebase.FirebaseOptions;
14
15
 
@@ -52,53 +53,6 @@ public class Configuration {
52
53
  List<Action> actions;
53
54
  }
54
55
 
55
- class InAppChatCustomization {
56
- //common
57
- String widgetTheme;
58
- String toolbarTitle;
59
- String toolbarTitleColor;
60
- String toolbarBackgroundColor;
61
- String chatBackgroundColor;
62
- String noConnectionAlertTextColor;
63
- String noConnectionAlertBackgroundColor;
64
- String chatInputPlaceholderTextColor;
65
- String chatInputCursorColor;
66
- String chatInputBackgroundColor;
67
- String sendButtonIconUri;
68
- String attachmentButtonIconUri;
69
- boolean chatInputSeparatorVisible;
70
- //android only
71
- //status bar properties
72
- boolean statusBarColorLight;
73
- String statusBarBackgroundColor;
74
- //toolbar properties
75
- String navigationIconUri;
76
- String navigationIconTint;
77
- String subtitleText;
78
- String subtitleTextColor;
79
- String subtitleTextAppearanceRes;
80
- boolean subtitleCentered;
81
- String titleTextAppearanceRes;
82
- boolean titleCentered;
83
- String menuItemsIconTint;
84
- String menuItemSaveAttachmentIcon;
85
- //chat properties
86
- String progressBarColor;
87
- String networkConnectionErrorTextAppearanceRes;
88
- String networkConnectionErrorText;
89
- //chat input properties
90
- String inputTextColor;
91
- String inputAttachmentIconTint;
92
- String inputAttachmentBackgroundColor;
93
- String inputAttachmentBackgroundDrawable;
94
- String inputSendIconTint;
95
- String inputSendBackgroundColor;
96
- String inputSendBackgroundDrawable;
97
- String inputSeparatorLineColor;
98
- String inputHintText;
99
- String inputTextAppearance;
100
- }
101
-
102
56
  class WebRTCUI {
103
57
  String configurationId;
104
58
  }
@@ -109,13 +63,13 @@ public class Configuration {
109
63
  boolean fullFeaturedInAppsEnabled;
110
64
  Map<String, ?> messageStorage;
111
65
  boolean defaultMessageStorage;
112
- boolean loggingEnabled;
66
+ boolean logging;
113
67
  String reactNativePluginVersion = "unknown";
114
68
  PrivacySettings privacySettings = new PrivacySettings();
115
69
  List<Category> notificationCategories = new ArrayList<Category>();
116
70
  WebRTCUI webRTCUI;
117
- InAppChatCustomization inAppChatCustomization;
118
71
  String userDataJwt;
72
+ @Nullable String backendBaseURL;
119
73
 
120
74
  @NonNull
121
75
  static Configuration resolveConfiguration(JSONObject args) throws JSONException {
@@ -130,18 +84,4 @@ public class Configuration {
130
84
 
131
85
  return config;
132
86
  }
133
-
134
- @NonNull
135
- static InAppChatCustomization resolveChatSettings(JSONObject args) throws JSONException {
136
- if (args == null) {
137
- throw new IllegalArgumentException("Cannot resolve configuration from arguments");
138
- }
139
-
140
- InAppChatCustomization config = new JsonSerializer().deserialize(args.toString(), InAppChatCustomization.class);
141
- if (config == null) {
142
- throw new IllegalArgumentException("Configuration is invalid");
143
- }
144
-
145
- return config;
146
- }
147
87
  }
@@ -22,7 +22,6 @@ import com.facebook.react.modules.core.PermissionAwareActivity;
22
22
  import com.facebook.react.modules.core.PermissionListener;
23
23
 
24
24
  import org.infobip.mobile.messaging.resources.R;
25
- import org.infobip.mobile.messaging.logging.MobileMessagingLogger;
26
25
  import org.infobip.mobile.messaging.permissions.PermissionsHelper;
27
26
 
28
27
  import java.util.Set;
@@ -155,7 +154,7 @@ public class PermissionsRequestManager {
155
154
  }
156
155
 
157
156
  protected void openSettings(Activity activity) {
158
- MobileMessagingLogger.d("Will open application settings activity");
157
+ RNMMLogger.d(Utils.TAG, "Will open application settings activity");
159
158
  Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
160
159
  Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
161
160
  intent.setData(uri);
@@ -8,7 +8,6 @@
8
8
 
9
9
  package org.infobip.reactlibrary.mobilemessaging
10
10
 
11
- import android.annotation.SuppressLint
12
11
  import android.app.Activity
13
12
  import android.content.BroadcastReceiver
14
13
  import android.content.Context
@@ -82,7 +81,7 @@ class RNMMChatService(
82
81
  override fun onReceive(context: Context, intent: Intent) {
83
82
  val event = broadcastEventMap[intent.action]
84
83
  if (event == null) {
85
- Log.w(TAG, "Cannot process event for broadcast: ${intent.action}")
84
+ RNMMLogger.w(TAG, "Cannot process event for broadcast: ${intent.action}")
86
85
  return
87
86
  }
88
87
 
@@ -135,6 +134,12 @@ class RNMMChatService(
135
134
  }
136
135
  }
137
136
 
137
+ fun isChatAvailable(onSuccess: Callback) {
138
+ runCatchingExceptions("isChatAvailable()") {
139
+ onSuccess.invoke(inAppChat.isChatAvailable())
140
+ }
141
+ }
142
+
138
143
  fun resetMessageCounter() {
139
144
  runCatchingExceptions("resetMessageCounter()") {
140
145
  inAppChat.resetMessageCounter()
@@ -174,7 +179,7 @@ class RNMMChatService(
174
179
  errorHandler = { t ->
175
180
  onError.invoke(Utils.callbackError(t.message, null))
176
181
  }
177
- )
182
+ )
178
183
  }
179
184
 
180
185
  fun setWidgetTheme(widgetTheme: String?) {
@@ -184,7 +189,6 @@ class RNMMChatService(
184
189
  }
185
190
 
186
191
  private val reactNativeDrawableLoader = object : PluginChatCustomization.DrawableLoader {
187
- @SuppressLint("LongLogTag")
188
192
  override fun loadDrawable(context: Context, drawableSrc: String?): Drawable? {
189
193
  if (drawableSrc.isNullOrBlank()) return null
190
194
  return try {
@@ -192,7 +196,7 @@ class RNMMChatService(
192
196
  BitmapDrawable(context.resources, drawableStream)
193
197
  }
194
198
  } catch (e: IOException) {
195
- Log.e("PluginChatCustomization.DrawableLoader", "Failed to load image $drawableSrc", e)
199
+ RNMMLogger.e("PluginChatCustomization.DrawableLoader", "Failed to load image $drawableSrc", e)
196
200
  null
197
201
  }
198
202
  }
@@ -227,7 +231,7 @@ class RNMMChatService(
227
231
  private fun sendRequestEvent() {
228
232
  reactContext
229
233
  ?.let { ReactNativeEvent.send(EVENT_INAPPCHAT_JWT_REQUESTED, it)}
230
- ?: Log.e(TAG, "React context is null, cannot send request for JWT.")
234
+ ?: RNMMLogger.e(TAG, "React context is null, cannot send request for JWT.")
231
235
  }
232
236
 
233
237
  fun requestJwt(callback: JwtProvider.JwtCallback) {
@@ -244,11 +248,11 @@ class RNMMChatService(
244
248
  updateAwaitingState()
245
249
  }
246
250
  reactContext?.runOnUiQueueThread(runnable) ?: run {
247
- Log.w(TAG, "React context is null, cannot resume with JWT value on UI thread.")
251
+ RNMMLogger.w(TAG, "React context is null, cannot resume with JWT value on UI thread.")
248
252
  runnable.run()
249
253
  }
250
254
  } catch (e: Exception) {
251
- Log.e(TAG, "Could not resume with JWT value $newJwt", e)
255
+ RNMMLogger.e(TAG, "Could not resume with JWT value $newJwt", e)
252
256
  }
253
257
  }
254
258
 
@@ -259,11 +263,11 @@ class RNMMChatService(
259
263
  updateAwaitingState()
260
264
  }
261
265
  reactContext?.runOnUiQueueThread(runnable) ?: run {
262
- Log.w(TAG, "React context is null, cannot resume with JWT error on UI thread.")
266
+ RNMMLogger.w(TAG, "React context is null, cannot resume with JWT error on UI thread.")
263
267
  runnable.run()
264
268
  }
265
269
  } catch (e: Exception) {
266
- Log.e(TAG, "Could not resume with JWT error ${throwable.message}", e)
270
+ RNMMLogger.e(TAG, "Could not resume with JWT error ${throwable.message}", e)
267
271
  }
268
272
  }
269
273
 
@@ -320,42 +324,46 @@ class RNMMChatService(
320
324
  override fun handleError(@NonNull exception: InAppChatException): Boolean {
321
325
  reactContext
322
326
  ?.let { ReactNativeEvent.send(EVENT_INAPPCHAT_EXCEPTION_RECEIVED, it, exception.toJSON())}
323
- ?: Log.e(Utils.TAG, "React context is null, cannot propagate chat exception.")
327
+ ?: RNMMLogger.e(Utils.TAG, "React context is null, cannot propagate chat exception.")
324
328
  return true
325
329
  }
326
330
  }
327
331
  }
332
+
333
+ fun setChatDomain(domain: String?) {
334
+ runCatchingExceptions("setChatDomain()", arrayOf(domain)) {
335
+ inAppChat.setDomain(domain)
336
+ }
337
+ }
328
338
  //endregion
329
339
 
330
340
  //region ActivityEventListener
331
- override fun onActivityResult(
332
- activity: Activity,
333
- requestCode: Int,
334
- resultCode: Int,
335
- data: Intent?
336
- ) {
341
+ override fun onActivityResult(activity: Activity?, requestCode: Int, resultCode: Int, data: Intent?) {
337
342
  val fragmentActivity = Utils.getFragmentActivity(reactContext) ?: return
338
343
  val fragment = fragmentActivity.supportFragmentManager.findFragmentByTag(Utils.RN_IN_APP_CHAT_FRAGMENT_TAG)
339
344
  if (fragment == null) {
340
- Log.w(TAG, "Can't find ${Utils.RN_IN_APP_CHAT_FRAGMENT_TAG} to provide onActivityResult")
345
+ RNMMLogger.w(TAG, "Can't find ${Utils.RN_IN_APP_CHAT_FRAGMENT_TAG} to provide onActivityResult")
341
346
  return
342
347
  }
343
348
  fragment.onActivityResult(requestCode and 0xffff, resultCode, data)
344
349
  }
345
350
 
351
+ override fun onNewIntent(intent: Intent?) {
352
+ // Activity `onNewIntent` - no-op
353
+ }
346
354
  //endregion
347
355
 
348
356
  //region LifecycleEventListener
349
357
  override fun onHostResume() {
350
- Log.d(TAG, "onHostResume()")
358
+ RNMMLogger.d(TAG, "onHostResume()")
351
359
  }
352
360
 
353
361
  override fun onHostPause() {
354
- Log.d(TAG, "onHostPause()")
362
+ RNMMLogger.d(TAG, "onHostPause()")
355
363
  }
356
364
 
357
365
  override fun onHostDestroy() {
358
- Log.d(TAG, "onHostDestroy()")
366
+ RNMMLogger.d(TAG, "onHostDestroy()")
359
367
  reactContext.removeActivityEventListener(this)
360
368
  reactContext.removeLifecycleEventListener(this)
361
369
  }
@@ -364,17 +372,13 @@ class RNMMChatService(
364
372
  //region Helpers
365
373
  private fun runCatchingExceptions(functionName: String, args: Array<out Any?> = emptyArray(), errorHandler: ((Throwable) -> Unit)? = null, block: () -> Unit) {
366
374
  val argsLog = if (args.isEmpty()) "" else " Arguments: ${args.joinToString()}"
367
- Log.d(TAG, "$functionName$argsLog")
375
+ RNMMLogger.d(TAG, "$functionName$argsLog")
368
376
  try {
369
377
  block()
370
378
  } catch (throwable: Throwable) {
371
- errorHandler?.invoke(throwable) ?: Log.e(TAG, "$functionName error: ${throwable.message}", throwable)
379
+ errorHandler?.invoke(throwable) ?: RNMMLogger.e(TAG, "$functionName error: ${throwable.message}", throwable)
372
380
  }
373
381
  }
374
-
375
- override fun onNewIntent(intent: Intent) {
376
- TODO("Not yet implemented")
377
- }
378
382
  //endregion
379
383
  }
380
384
 
@@ -386,13 +390,11 @@ class RNMMChatEventReceiver : ReactNativeBroadcastReceiver() {
386
390
 
387
391
  override fun onReceive(context: Context?, intent: Intent?) {
388
392
  if (InAppChatEvent.UNREAD_MESSAGES_COUNTER_UPDATED.key != intent?.action) {
389
- Log.w(TAG, "Cannot process event for broadcast: ${intent?.action}")
393
+ RNMMLogger.w(TAG, "Cannot process event for broadcast: ${intent?.action}")
390
394
  return
391
395
  }
392
- val unreadChatMessagesCounter = intent?.getIntExtra(BroadcastParameter.EXTRA_UNREAD_CHAT_MESSAGES_COUNT, 0)
393
- if (unreadChatMessagesCounter != null) {
394
- emitOrCache(context, RNMMChatService.EVENT_INAPPCHAT_UNREAD_MESSAGES_COUNT_UPDATED, unreadChatMessagesCounter)
395
- }
396
+ val unreadChatMessagesCounter = intent.getIntExtra(BroadcastParameter.EXTRA_UNREAD_CHAT_MESSAGES_COUNT, 0)
397
+ emitOrCache(context, RNMMChatService.EVENT_INAPPCHAT_UNREAD_MESSAGES_COUNT_UPDATED, unreadChatMessagesCounter)
396
398
  }
397
399
 
398
400
  private fun emitOrCache(context: Context?, eventType: String, unreadMessagesCounter: Int) {
@@ -406,7 +408,7 @@ class RNMMChatEventReceiver : ReactNativeBroadcastReceiver() {
406
408
  } else if (context != null) {
407
409
  CacheManager.saveEvent(context, eventType, unreadMessagesCounter)
408
410
  } else {
409
- Log.e(TAG, "Both reactContext and androidContext are null, can't emit or cache event " + eventType)
411
+ RNMMLogger.e(TAG, "Both reactContext and androidContext are null, can't emit or cache event " + eventType)
410
412
  }
411
413
  }
412
414
  }
@@ -66,7 +66,7 @@ class RNMMChatView @JvmOverloads constructor(
66
66
  if (parent is ViewGroup) {
67
67
  setupLayoutHack(parent)
68
68
  } else {
69
- Log.e(TAG, "Parent is not ViewGroup, cannot show InAppChatFragment.")
69
+ RNMMLogger.e(TAG, "Parent is not ViewGroup, cannot show InAppChatFragment.")
70
70
  }
71
71
 
72
72
  val fragmentManager = fragmentActivity?.supportFragmentManager
@@ -75,7 +75,7 @@ class RNMMChatView @JvmOverloads constructor(
75
75
  .replace(this.id, fragment, Utils.RN_IN_APP_CHAT_FRAGMENT_TAG)
76
76
  .commitNow()
77
77
  } else {
78
- Log.e(TAG, "FragmentManager is null, cannot add InAppChatFragment.")
78
+ RNMMLogger.e(TAG, "FragmentManager is null, cannot add InAppChatFragment.")
79
79
  }
80
80
  }
81
81
  }
@@ -91,7 +91,7 @@ class RNMMChatView @JvmOverloads constructor(
91
91
  .commitNow()
92
92
  cachedFragment = null
93
93
  } else {
94
- Log.e(TAG, "InAppChatFragment or FragmentManager is null, cannot remove InAppChatFragment.")
94
+ RNMMLogger.e(TAG, "InAppChatFragment or FragmentManager is null, cannot remove InAppChatFragment.")
95
95
  }
96
96
  }
97
97
  }
@@ -102,7 +102,7 @@ class RNMMChatView @JvmOverloads constructor(
102
102
  if (fragment != null) {
103
103
  fragment.showThreadList()
104
104
  } else {
105
- Log.e(TAG, "InAppChatFragment is null, cannot show threads list.")
105
+ RNMMLogger.e(TAG, "InAppChatFragment is null, cannot show threads list.")
106
106
  }
107
107
  }
108
108
  }
@@ -133,11 +133,11 @@ class RNMMChatView @JvmOverloads constructor(
133
133
  //region Helpers
134
134
  private fun runCatchingExceptions(functionName: String, args: Array<out Any?> = emptyArray(), errorHandler: ((Throwable) -> Unit)? = null, block: () -> Unit) {
135
135
  val argsLog = if (args.isEmpty()) "" else " Arguments: ${args.joinToString()}"
136
- Log.d(TAG, "$functionName$argsLog")
136
+ RNMMLogger.d(TAG, "$functionName$argsLog")
137
137
  try {
138
138
  block()
139
139
  } catch (throwable: Throwable) {
140
- errorHandler?.invoke(throwable) ?: Log.e(TAG, "$functionName error: ${throwable.message}", throwable)
140
+ errorHandler?.invoke(throwable) ?: RNMMLogger.e(TAG, "$functionName error: ${throwable.message}", throwable)
141
141
  }
142
142
  }
143
143
 
@@ -0,0 +1,91 @@
1
+ //
2
+ // RNMMLogWriter.kt
3
+ // MobileMessagingReactNative
4
+ //
5
+ // Copyright (c) 2016-2025 Infobip Limited
6
+ // Licensed under the Apache License, Version 2.0
7
+ //
8
+
9
+ package org.infobip.reactlibrary.mobilemessaging
10
+
11
+ import android.util.Log
12
+ import com.facebook.react.bridge.ReactContext
13
+ import org.infobip.mobile.messaging.logging.Writer
14
+ import org.infobip.mobile.messaging.logging.Level
15
+ import org.infobip.mobile.messaging.logging.LogcatWriter
16
+ import org.infobip.reactlibrary.mobilemessaging.datamappers.ReactNativeJson
17
+ import org.json.JSONObject
18
+ import java.text.SimpleDateFormat
19
+ import java.util.Date
20
+ import java.util.Locale
21
+
22
+ /**
23
+ * Custom log writer that proxies Android native SDK logs to React Native console
24
+ * Implements the Writer interface from MobileMessagingLogger
25
+ */
26
+ class RNMMLogWriter(private val reactContext: ReactContext?) : Writer {
27
+
28
+ companion object {
29
+ private val dateFormat = SimpleDateFormat("HH:mm:ss.SSS", Locale.US)
30
+ }
31
+
32
+ private val logcatWriter: LogcatWriter = LogcatWriter()
33
+
34
+ /**
35
+ * Called by MobileMessagingLogger for each log entry
36
+ * Converts log level to prefix and emits event to React Native
37
+ *
38
+ * @param level Log level (VERBOSE, DEBUG, INFO, WARN, ERROR)
39
+ * @param tag Log tag (usually SDK component name)
40
+ * @param message Log message
41
+ * @param throwable Optional exception/throwable
42
+ */
43
+ override fun write(level: Level, tag: String, message: String, throwable: Throwable?) {
44
+ write(level.name, tag, message, throwable)
45
+ }
46
+
47
+ /**
48
+ * Called by RNMMLogger for each log entry
49
+ * Converts log level to prefix and emits event to React Native
50
+ *
51
+ * @param level Log level (VERBOSE, DEBUG, INFO, WARN, ERROR)
52
+ * @param tag Log tag (usually SDK component name)
53
+ * @param message Log message
54
+ * @param throwable Optional exception/throwable
55
+ */
56
+ fun write(level: String?, tag: String?, message: String?, throwable: Throwable?) {
57
+ if (message.isNullOrBlank())
58
+ return
59
+
60
+ if (reactContext == null) {
61
+ // Fallback to Logcat if message cannot be logged by RN console
62
+ runCatching {
63
+ logcatWriter.write(Level.valueOf(level ?: Level.DEBUG.name), tag, message, throwable)
64
+ }.onFailure {
65
+ logcatWriter.write(Level.DEBUG, tag, message, throwable)
66
+ }
67
+ return
68
+ }
69
+
70
+ val timestamp = dateFormat.format(Date())
71
+ val fullMessage = if (throwable != null) {
72
+ "$message\n${Log.getStackTraceString(throwable)}"
73
+ } else {
74
+ message
75
+ }
76
+ val tagLog = if(tag?.isNullOrBlank() == true) "" else " [$tag]"
77
+ val logcatStyleMessage = "$timestamp$tagLog: $fullMessage"
78
+ val prefixedMessage = when (level) {
79
+ Level.WARN.name -> "RNMMWARN: $logcatStyleMessage"
80
+ Level.ERROR.name -> "RNMMERROR: $logcatStyleMessage"
81
+ else -> logcatStyleMessage
82
+ }
83
+ val payload = JSONObject().apply {
84
+ put("message", prefixedMessage)
85
+ }
86
+ val payloadMap = ReactNativeJson.convertJsonToMap(payload)
87
+ ReactNativeEvent.send(ReactNativeMobileMessagingService.EVENT_PLATFORM_NATIVE_LOG_SENT, reactContext, payloadMap)
88
+ }
89
+
90
+
91
+ }
@@ -0,0 +1,82 @@
1
+ //
2
+ // RNMMLogger.kt
3
+ // MobileMessagingReactNative
4
+ //
5
+ // Copyright (c) 2016-2025 Infobip Limited
6
+ // Licensed under the Apache License, Version 2.0
7
+ //
8
+
9
+ package org.infobip.reactlibrary.mobilemessaging
10
+
11
+ import org.infobip.mobile.messaging.logging.Level
12
+ import android.util.Log
13
+
14
+ /**
15
+ * Centralized logging utility for React Native plugin code.
16
+ * Routes logs to EITHER React Native console OR Android Logcat (mutually exclusive).
17
+ */
18
+ object RNMMLogger {
19
+
20
+ private var writer: RNMMLogWriter? = null
21
+
22
+ /**
23
+ * Route logs to React Native console via RNMMLogWriter.
24
+ * When enabled, logs will NOT appear in Android Logcat.
25
+ */
26
+ fun useReactNativeConsole(writer: RNMMLogWriter) {
27
+ this.writer = writer
28
+ }
29
+
30
+ /**
31
+ * Route logs to Android Logcat (default behavior).
32
+ * When enabled, logs will NOT appear in React Native console.s
33
+ */
34
+ fun useNativeLogcat() {
35
+ this.writer = null
36
+ }
37
+
38
+ /**
39
+ * Log a VERBOSE message.
40
+ */
41
+ @JvmStatic
42
+ @JvmOverloads
43
+ fun v(tag: String, message: String, throwable: Throwable? = null) {
44
+ writer?.write(Level.VERBOSE.name, tag, message, throwable) ?: Log.v(tag, message, throwable)
45
+ }
46
+
47
+ /**
48
+ * Log a DEBUG message.
49
+ */
50
+ @JvmStatic
51
+ @JvmOverloads
52
+ fun d(tag: String, message: String, throwable: Throwable? = null) {
53
+ writer?.write(Level.DEBUG.name, tag, message, throwable) ?: Log.d(tag, message, throwable)
54
+ }
55
+
56
+ /**
57
+ * Log an INFO message.
58
+ */
59
+ @JvmStatic
60
+ @JvmOverloads
61
+ fun i(tag: String, message: String, throwable: Throwable? = null) {
62
+ writer?.write(Level.INFO.name, tag, message, throwable) ?: Log.i(tag, message, throwable)
63
+ }
64
+
65
+ /**
66
+ * Log a WARN message.
67
+ */
68
+ @JvmStatic
69
+ @JvmOverloads
70
+ fun w(tag: String, message: String, throwable: Throwable? = null) {
71
+ writer?.write(Level.WARN.name, tag, message, throwable) ?: Log.w(tag, message, throwable)
72
+ }
73
+
74
+ /**
75
+ * Log an ERROR message.
76
+ */
77
+ @JvmStatic
78
+ @JvmOverloads
79
+ fun e(tag: String, message: String, throwable: Throwable? = null) {
80
+ writer?.write(Level.ERROR.name, tag, message, throwable) ?: Log.e(tag, message, throwable)
81
+ }
82
+ }
@@ -24,6 +24,67 @@ class RNMMWebRTCUIService(
24
24
 
25
25
  companion object {
26
26
  private var infobipRtcUiInstance: Any? = null
27
+ const val TAG = "RNMMWebRTCUI"
28
+
29
+ fun resetLogger(reactContext: ReactApplicationContext?) {
30
+ if (reactContext == null) {
31
+ RNMMLogger.w(TAG, "ReactContext is null, cannot enable native logs for WebRTCUI")
32
+ return
33
+ }
34
+ try {
35
+ val rtcUiLoggerClass = Class.forName("com.infobip.webrtc.ui.logging.RtcUiLogger")
36
+ val rtcUiLoggerInstance = rtcUiLoggerClass.getField("INSTANCE").get(null)
37
+
38
+ val resetMethod = rtcUiLoggerClass.getDeclaredMethod("reset")
39
+ resetMethod.isAccessible = true
40
+ resetMethod.invoke(rtcUiLoggerInstance)
41
+ } catch (e: ClassNotFoundException) {
42
+ // Ignored - Android WebRtcUi not enabled - no logs to enable
43
+ } catch (t: Throwable) {
44
+ RNMMLogger.e(TAG, "Cannot reset logger for WebRTCUI. Something went wrong.", t)
45
+ }
46
+ }
47
+
48
+ fun enforceLogsWriter(reactContext: ReactApplicationContext?, writer: RNMMLogWriter) {
49
+ if (reactContext == null) {
50
+ RNMMLogger.w(TAG, "ReactContext is null, cannot enable native logs for WebRTCUI")
51
+ return
52
+ }
53
+ try {
54
+ val rtcUiLoggerClass = Class.forName("com.infobip.webrtc.ui.logging.RtcUiLogger")
55
+ val rtcUiLoggerInstance = rtcUiLoggerClass.getField("INSTANCE").get(null)
56
+
57
+ val initMethod = rtcUiLoggerClass.getDeclaredMethod("init", Context::class.java)
58
+ initMethod.isAccessible = true
59
+ initMethod.invoke(rtcUiLoggerInstance, reactContext.applicationContext)
60
+
61
+ val enforceMethod = rtcUiLoggerClass.getDeclaredMethod("enforce")
62
+ enforceMethod.isAccessible = true
63
+ enforceMethod.invoke(rtcUiLoggerInstance)
64
+
65
+ val rtcUiWriterClass = Class.forName("com.infobip.webrtc.ui.logging.RtcUiWriter")
66
+ val writerInstance = java.lang.reflect.Proxy.newProxyInstance(rtcUiWriterClass.classLoader, arrayOf(rtcUiWriterClass)) { _, method, args ->
67
+ if ("write" == method.name) {
68
+ val level: Any? = args?.getOrNull(0)
69
+ val levelName: String? = (level as? Enum<*>)?.name
70
+ val tag: String = args?.getOrNull(1) as? String ?: ""
71
+ val message: String = args?.getOrNull(2) as? String ?: ""
72
+ val throwable: Throwable? = args?.getOrNull(3) as? Throwable
73
+ writer.write(levelName, tag, message, throwable)
74
+ }
75
+ null
76
+ }
77
+
78
+ val setWriterMethod = rtcUiLoggerClass.getDeclaredMethod("setWriter", rtcUiWriterClass)
79
+ setWriterMethod.isAccessible = true
80
+ setWriterMethod.invoke(rtcUiLoggerInstance, writerInstance)
81
+ } catch (e: ClassNotFoundException) {
82
+ // Ignored - Android WebRtcUi not enabled - no logs to enable
83
+ } catch (t: Throwable) {
84
+ RNMMLogger.e(TAG, "Cannot enable native logs for WebRTCUI. Something went wrong.", t)
85
+ }
86
+ }
87
+
27
88
  }
28
89
 
29
90
  private var successListenerClass: Class<*>? = null