infobip-mobile-messaging-react-native-plugin 14.7.0 → 14.8.3

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.
package/README.md CHANGED
@@ -12,7 +12,7 @@ The document describes library integration steps for your React Native project.
12
12
  ## Requirements
13
13
  - node (v20.16.0 or higher)
14
14
  - ruby (2.7.8 or higher; the Example app uses 3.3.5)
15
- - React Native (v0.79.0)
15
+ - React Native (v0.84.0)
16
16
 
17
17
  For iOS project:
18
18
  - Xcode and Command Line Tools (16.x or newer, tested with 26.0.1)
@@ -1,7 +1,7 @@
1
1
  require "json"
2
2
 
3
3
  package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
- mmVersion = "14.4.3"
4
+ mmVersion = "15.0.0"
5
5
 
6
6
  Pod::Spec.new do |s|
7
7
  s.name = "infobip-mobile-messaging-react-native-plugin"
@@ -13,9 +13,15 @@ import MobileMessaging
13
13
  class RNMMChat: NSObject {
14
14
  private var willUseJWT = false
15
15
  private var willUseChatExceptionHandler = false
16
- private var jwtRequestQueue: [((String?) -> Void)] = []
16
+ private var jwtRequestQueue: [JWTRequest] = []
17
17
  private let jwtQueueLock = NSLock()
18
18
 
19
+ private final class JWTRequest {
20
+ var completed = false
21
+ let onResult: (String?) -> Void
22
+ init(_ onResult: @escaping (String?) -> Void) { self.onResult = onResult }
23
+ }
24
+
19
25
  @objc(showChat:)
20
26
  func showChat(presentingOptions: NSDictionary) {
21
27
  MobileMessaging.inAppChat?.delegate = self
@@ -161,22 +167,26 @@ class RNMMChat: NSObject {
161
167
  @objc(setChatJwtProvider)
162
168
  func setChatJwtProvider() {
163
169
  willUseJWT = true
170
+ // We cannot rely on 'showChat' for setting the delegate as we have several presentation modes - any async provider/handler request sets the delegate to ensure proper completions are triggered
171
+ MobileMessaging.inAppChat?.delegate = self
164
172
  }
165
173
 
166
174
  @objc(setChatExceptionHandler:)
167
175
  func setChatExceptionHandler(isHandlerPresent: NSNumber) {
168
176
  // NSNumber due to how RN bridge wraps JavaScript booleans
169
177
  willUseChatExceptionHandler = isHandlerPresent.boolValue
178
+ MobileMessaging.inAppChat?.delegate = self
170
179
  }
171
180
 
172
181
  @objc(setChatJwt:)
173
182
  func setChatJwt(jwt: String?) {
174
183
  jwtQueueLock.lock()
175
- if !jwtRequestQueue.isEmpty {
176
- let completion = jwtRequestQueue.removeFirst()
177
- completion(jwt)
178
- }
179
- jwtQueueLock.unlock()
184
+ defer { jwtQueueLock.unlock() }
185
+ guard let request = jwtRequestQueue.first(where: { !$0.completed }) else { return }
186
+ request.completed = true
187
+ jwtRequestQueue.removeAll(where: { $0 === request })
188
+ // Settled inside the lock so a concurrent getJWT cleanup observes completed == true and jwtResult is set before getJWT returns.
189
+ request.onResult(jwt)
180
190
  }
181
191
 
182
192
  @objc(restartConnection)
@@ -201,15 +211,27 @@ extension RNMMChat: MMInAppChatDelegate {
201
211
  guard willUseJWT else { return nil }
202
212
  var jwtResult: String?
203
213
  let semaphore = DispatchSemaphore(value: 0)
204
-
205
- jwtQueueLock.lock()
206
- jwtRequestQueue.append { jwt in
214
+ let request = JWTRequest { jwt in
207
215
  jwtResult = jwt
208
216
  semaphore.signal()
209
217
  }
218
+
219
+ jwtQueueLock.lock()
220
+ jwtRequestQueue.append(request)
210
221
  jwtQueueLock.unlock()
211
- ReactNativeMobileMessaging.shared?.sendEvent(withName: EventName.inAppChat_jwtRequested, body: nil)
222
+
223
+ ReactNativeMobileMessaging.shared?.sendDirectEvent(withName: EventName.inAppChat_jwtRequested, body: nil)
212
224
  _ = semaphore.wait(timeout: .now() + 45)
225
+
226
+ // Remove this request by identity (not position) so a setChatJwt that fires between the wait timeout and the lock cannot cause us to evict a different in-flight request.
227
+ jwtQueueLock.lock()
228
+ if !request.completed {
229
+ request.completed = true
230
+ if let idx = jwtRequestQueue.firstIndex(where: { $0 === request }) {
231
+ jwtRequestQueue.remove(at: idx)
232
+ }
233
+ }
234
+ jwtQueueLock.unlock()
213
235
  return jwtResult
214
236
  }
215
237
 
@@ -37,7 +37,9 @@ class RNMMChatViewManager: RCTViewManager {
37
37
  }
38
38
 
39
39
  override func didMoveToWindow() {
40
- embedViewController()
40
+ if window != nil { // this could be triggered in an improper manner from RN side, so we confirm UI is in a proper state
41
+ embedViewController()
42
+ }
41
43
  super.didMoveToWindow()
42
44
  }
43
45
 
@@ -64,6 +64,20 @@ class ReactNativeMobileMessaging: RCTEventEmitter {
64
64
  }
65
65
  super.sendEvent(withName: name, body: body)
66
66
  }
67
+
68
+ func sendDirectEvent(withName name: String!, body: Any!) {
69
+ guard let _eventsManager = eventsManager, _eventsManager.hasEventListeners == true else {
70
+ // JS listener persists on the mobileMessaging singleton but RCTEventEmitter's _listenerCount may be zero during a React Navigation transition (reproducible with custom chat navigations and JWT async callback/authentication flow). Bypass that guard by invoking RCTDeviceEventEmitter directly via the public callableJSModules API (bridgeless-safe; the same path super.sendEvent uses internally).
71
+ MMLogDebug("[RNMobileMessaging] sendDirectEvent: invoking RCTDeviceEventEmitter directly")
72
+ self.callableJSModules?.invokeModule(
73
+ "RCTDeviceEventEmitter",
74
+ method: "emit",
75
+ withArgs: [name!, body ?? NSNull()]
76
+ )
77
+ return
78
+ }
79
+ super.sendEvent(withName: name, body: body)
80
+ }
67
81
 
68
82
  @objc
69
83
  override static func requiresMainQueueSetup() -> Bool {
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": "14.7.0",
4
+ "version": "14.8.3",
5
5
  "description": "Infobip Mobile Messaging React Native Plugin",
6
6
  "main": "./src/index.js",
7
7
  "scripts": {