@capillarytech/react-native-hydra-lib 1.7.0-beta.1 → 1.7.0-beta.10

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 (153) hide show
  1. package/android/build.gradle +12 -13
  2. package/android/src/main/java/com/hydralib/delegates/NonHydraNotificationActivity.kt +22 -10
  3. package/android/src/main/java/com/hydralib/eventemitter/HydraEventEmitter.kt +34 -4
  4. package/android/src/main/java/com/hydralib/eventemitter/HydraEventType.kt +5 -1
  5. package/android/src/main/java/com/hydralib/gamification/GamificationBannerViewManager.kt +34 -3
  6. package/android/src/main/java/com/hydralib/gamification/GamificationEmbedViewManager.kt +25 -4
  7. package/android/src/main/java/com/hydralib/gamification/GamificationLoyaltyViewManager.kt +41 -6
  8. package/android/src/main/java/com/hydralib/hydrasdk/HydraCore.kt +109 -6
  9. package/android/src/main/java/com/hydralib/hydrasdk/HydraInAppMessages.kt +68 -41
  10. package/android/src/main/java/com/hydralib/hydrasdk/HydraInbox.kt +2 -2
  11. package/android/src/main/java/com/hydralib/hydrasdk/HydraPushNotification.kt +29 -1
  12. package/android/src/newarch/HydraLibModule.kt +16 -0
  13. package/android/src/oldarch/HydraLibModule.kt +20 -0
  14. package/ios/Delegates/HydraApplicationDelegate.swift +8 -0
  15. package/ios/EventEmitter/HydraEventType.swift +2 -0
  16. package/ios/GamificationViews/GamificationBannerViewComponentView.mm +43 -6
  17. package/ios/GamificationViews/GamificationEmbedViewComponentView.mm +37 -6
  18. package/ios/GamificationViews/GamificationLoyaltyViewComponentView.mm +45 -5
  19. package/ios/HydraLib.h +3 -4
  20. package/ios/HydraLib.mm +16 -0
  21. package/ios/HydraSDK/HydraInAppMessages.swift +35 -8
  22. package/ios/HydraSDK/HydraInbox.swift +36 -9
  23. package/ios/HydraSDK/HydraInboxUI.swift +5 -1
  24. package/ios/HydraSDK/HydraPushNotification.swift +2 -2
  25. package/ios/HydraSDK/HydraSDKCore.swift +185 -82
  26. package/ios/Utils/HydraExtensions.swift +1 -1
  27. package/lib/commonjs/GamificationBannerViewNativeComponent.ts +10 -0
  28. package/lib/commonjs/GamificationEmbedViewNativeComponent.ts +10 -0
  29. package/lib/commonjs/GamificationLoyaltyViewNativeComponent.ts +10 -0
  30. package/lib/commonjs/NativeHydraLib.ts +85 -0
  31. package/lib/commonjs/components/GamificationBannerView.js +1 -1
  32. package/lib/commonjs/components/GamificationEmbedView.js +1 -1
  33. package/lib/commonjs/components/GamificationLoyaltyView.js +1 -1
  34. package/lib/commonjs/hydra-apis/Hydra.js +14 -1
  35. package/lib/commonjs/hydra-apis/Hydra.js.map +1 -1
  36. package/lib/commonjs/hydra-apis/HydraCore.js +1 -1
  37. package/lib/commonjs/hydra-apis/HydraInAppMessaging.js +1 -1
  38. package/lib/commonjs/hydra-apis/HydraInbox.js +1 -1
  39. package/lib/commonjs/hydra-apis/HydraInboxUI.js +1 -1
  40. package/lib/commonjs/hydra-apis/HydraNativeEventEmitter.js +11 -1
  41. package/lib/commonjs/hydra-apis/HydraNativeEventEmitter.js.map +1 -1
  42. package/lib/commonjs/hydra-apis/HydraPushNotification.js +1 -1
  43. package/lib/commonjs/index.js +7 -0
  44. package/lib/commonjs/index.js.map +1 -1
  45. package/lib/commonjs/models/HydraNativeEvent.js +2 -0
  46. package/lib/commonjs/models/HydraNativeEvent.js.map +1 -1
  47. package/lib/commonjs/models/ThemeMode.js +14 -0
  48. package/lib/commonjs/models/ThemeMode.js.map +1 -0
  49. package/lib/module/GamificationBannerViewNativeComponent.ts +10 -0
  50. package/lib/module/GamificationEmbedViewNativeComponent.ts +10 -0
  51. package/lib/module/GamificationLoyaltyViewNativeComponent.ts +10 -0
  52. package/lib/module/NativeHydraLib.ts +85 -0
  53. package/lib/module/components/GamificationBannerView.js +1 -1
  54. package/lib/module/components/GamificationEmbedView.js +1 -1
  55. package/lib/module/components/GamificationLoyaltyView.js +1 -1
  56. package/lib/module/hydra-apis/Hydra.js +14 -1
  57. package/lib/module/hydra-apis/Hydra.js.map +1 -1
  58. package/lib/module/hydra-apis/HydraCore.js +1 -1
  59. package/lib/module/hydra-apis/HydraInAppMessaging.js +1 -1
  60. package/lib/module/hydra-apis/HydraInbox.js +1 -1
  61. package/lib/module/hydra-apis/HydraInboxUI.js +1 -1
  62. package/lib/module/hydra-apis/HydraNativeEventEmitter.js +11 -1
  63. package/lib/module/hydra-apis/HydraNativeEventEmitter.js.map +1 -1
  64. package/lib/module/hydra-apis/HydraPushNotification.js +1 -1
  65. package/lib/module/index.js +2 -1
  66. package/lib/module/index.js.map +1 -1
  67. package/lib/module/models/HydraNativeEvent.js +2 -0
  68. package/lib/module/models/HydraNativeEvent.js.map +1 -1
  69. package/lib/module/models/ThemeMode.js +10 -0
  70. package/lib/module/models/ThemeMode.js.map +1 -0
  71. package/lib/typescript/commonjs/src/NativeHydraLib.d.ts +4 -0
  72. package/lib/typescript/commonjs/src/NativeHydraLib.d.ts.map +1 -1
  73. package/lib/typescript/commonjs/src/hydra-apis/Hydra.d.ts +5 -0
  74. package/lib/typescript/commonjs/src/hydra-apis/Hydra.d.ts.map +1 -1
  75. package/lib/typescript/commonjs/src/hydra-apis/HydraNativeEventEmitter.d.ts +2 -0
  76. package/lib/typescript/commonjs/src/hydra-apis/HydraNativeEventEmitter.d.ts.map +1 -1
  77. package/lib/typescript/commonjs/src/index.d.ts +2 -1
  78. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  79. package/lib/typescript/commonjs/src/models/HydraNativeEvent.d.ts +3 -1
  80. package/lib/typescript/commonjs/src/models/HydraNativeEvent.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/src/models/ThemeMode.d.ts +7 -0
  82. package/lib/typescript/commonjs/src/models/ThemeMode.d.ts.map +1 -0
  83. package/lib/typescript/module/src/NativeHydraLib.d.ts +4 -0
  84. package/lib/typescript/module/src/NativeHydraLib.d.ts.map +1 -1
  85. package/lib/typescript/module/src/hydra-apis/Hydra.d.ts +5 -0
  86. package/lib/typescript/module/src/hydra-apis/Hydra.d.ts.map +1 -1
  87. package/lib/typescript/module/src/hydra-apis/HydraNativeEventEmitter.d.ts +2 -0
  88. package/lib/typescript/module/src/hydra-apis/HydraNativeEventEmitter.d.ts.map +1 -1
  89. package/lib/typescript/module/src/index.d.ts +2 -1
  90. package/lib/typescript/module/src/index.d.ts.map +1 -1
  91. package/lib/typescript/module/src/models/HydraNativeEvent.d.ts +3 -1
  92. package/lib/typescript/module/src/models/HydraNativeEvent.d.ts.map +1 -1
  93. package/lib/typescript/module/src/models/ThemeMode.d.ts +7 -0
  94. package/lib/typescript/module/src/models/ThemeMode.d.ts.map +1 -0
  95. package/package.json +13 -4
  96. package/react-native-hydra-lib.podspec +9 -1
  97. package/react-native.config.js +9 -23
  98. package/sbom.spdx.json +4 -4
  99. package/scripts/HydraPods.rb +1 -1
  100. package/scripts/preserve-codegen-specs.js +117 -0
  101. package/src/NativeHydraLib.ts +4 -0
  102. package/src/hydra-apis/Hydra.ts +18 -0
  103. package/src/hydra-apis/HydraNativeEventEmitter.ts +20 -0
  104. package/src/index.tsx +2 -0
  105. package/src/models/HydraNativeEvent.ts +2 -0
  106. package/src/models/ThemeMode.ts +6 -0
  107. package/android/generated/java/com/hydralib/NativeHydraLibSpec.java +0 -185
  108. package/android/generated/jni/CMakeLists.txt +0 -36
  109. package/android/generated/jni/RNHydraLibSpec-generated.cpp +0 -248
  110. package/android/generated/jni/RNHydraLibSpec.h +0 -31
  111. package/android/generated/jni/react/renderer/components/RNHydraLibSpec/ComponentDescriptors.cpp +0 -24
  112. package/android/generated/jni/react/renderer/components/RNHydraLibSpec/ComponentDescriptors.h +0 -26
  113. package/android/generated/jni/react/renderer/components/RNHydraLibSpec/EventEmitters.cpp +0 -18
  114. package/android/generated/jni/react/renderer/components/RNHydraLibSpec/EventEmitters.h +0 -37
  115. package/android/generated/jni/react/renderer/components/RNHydraLibSpec/Props.cpp +0 -39
  116. package/android/generated/jni/react/renderer/components/RNHydraLibSpec/Props.h +0 -47
  117. package/android/generated/jni/react/renderer/components/RNHydraLibSpec/RNHydraLibSpecJSI-generated.cpp +0 -283
  118. package/android/generated/jni/react/renderer/components/RNHydraLibSpec/RNHydraLibSpecJSI.h +0 -395
  119. package/android/generated/jni/react/renderer/components/RNHydraLibSpec/ShadowNodes.cpp +0 -19
  120. package/android/generated/jni/react/renderer/components/RNHydraLibSpec/ShadowNodes.h +0 -54
  121. package/android/generated/jni/react/renderer/components/RNHydraLibSpec/States.cpp +0 -16
  122. package/android/generated/jni/react/renderer/components/RNHydraLibSpec/States.h +0 -53
  123. package/ios/generated/RNHydraLibSpec/ComponentDescriptors.cpp +0 -24
  124. package/ios/generated/RNHydraLibSpec/ComponentDescriptors.h +0 -26
  125. package/ios/generated/RNHydraLibSpec/EventEmitters.cpp +0 -18
  126. package/ios/generated/RNHydraLibSpec/EventEmitters.h +0 -37
  127. package/ios/generated/RNHydraLibSpec/Props.cpp +0 -39
  128. package/ios/generated/RNHydraLibSpec/Props.h +0 -47
  129. package/ios/generated/RNHydraLibSpec/RCTComponentViewHelpers.h +0 -28
  130. package/ios/generated/RNHydraLibSpec/RNHydraLibSpec-generated.mm +0 -291
  131. package/ios/generated/RNHydraLibSpec/RNHydraLibSpec.h +0 -167
  132. package/ios/generated/RNHydraLibSpec/ShadowNodes.cpp +0 -19
  133. package/ios/generated/RNHydraLibSpec/ShadowNodes.h +0 -54
  134. package/ios/generated/RNHydraLibSpec/States.cpp +0 -16
  135. package/ios/generated/RNHydraLibSpec/States.h +0 -53
  136. package/ios/generated/RNHydraLibSpecJSI-generated.cpp +0 -283
  137. package/ios/generated/RNHydraLibSpecJSI.h +0 -395
  138. package/lib/commonjs/GamificationBannerViewNativeComponent.js +0 -10
  139. package/lib/commonjs/GamificationBannerViewNativeComponent.js.map +0 -1
  140. package/lib/commonjs/GamificationEmbedViewNativeComponent.js +0 -10
  141. package/lib/commonjs/GamificationEmbedViewNativeComponent.js.map +0 -1
  142. package/lib/commonjs/GamificationLoyaltyViewNativeComponent.js +0 -10
  143. package/lib/commonjs/GamificationLoyaltyViewNativeComponent.js.map +0 -1
  144. package/lib/commonjs/NativeHydraLib.js +0 -11
  145. package/lib/commonjs/NativeHydraLib.js.map +0 -1
  146. package/lib/module/GamificationBannerViewNativeComponent.js +0 -5
  147. package/lib/module/GamificationBannerViewNativeComponent.js.map +0 -1
  148. package/lib/module/GamificationEmbedViewNativeComponent.js +0 -5
  149. package/lib/module/GamificationEmbedViewNativeComponent.js.map +0 -1
  150. package/lib/module/GamificationLoyaltyViewNativeComponent.js +0 -5
  151. package/lib/module/GamificationLoyaltyViewNativeComponent.js.map +0 -1
  152. package/lib/module/NativeHydraLib.js +0 -7
  153. package/lib/module/NativeHydraLib.js.map +0 -1
@@ -66,33 +66,46 @@ object HydraInAppMessages {
66
66
  message: ReadableMap?,
67
67
  promise: Promise?
68
68
  ) {
69
-
70
- message?.let {
71
- val messageMap = message.toMap()
72
- messageMap["nativeJsonPayload"]?.let {
73
- val jsonString = Gson().toJson(it)
74
- val payloadFromJson = Gson().fromJson(jsonString, CampaignPayload::class.java)
75
- HydraInAppUIHelper.getInstance()
76
- .reportInAppMessageIsClicked(HydraCore.appContext!!, payloadFromJson)
77
- promise?.resolve(true)
78
- }
69
+ if (message == null) {
70
+ Logger.e("HydraInAppMessages.reportMessageClicked", "message is null, cannot report click")
71
+ promise?.reject("Error", "message is null")
72
+ return
73
+ }
74
+ val messageMap = message.toMap()
75
+ val nativeJsonPayload = messageMap["nativeJsonPayload"]
76
+ if (nativeJsonPayload == null) {
77
+ Logger.e("HydraInAppMessages.reportMessageClicked", "nativeJsonPayload missing in message, cannot report click")
78
+ promise?.reject("Error", "nativeJsonPayload missing in message")
79
+ return
79
80
  }
81
+ val jsonString = Gson().toJson(nativeJsonPayload)
82
+ val payloadFromJson = Gson().fromJson(jsonString, CampaignPayload::class.java)
83
+ HydraInAppUIHelper.getInstance()
84
+ .reportInAppMessageIsClicked(HydraCore.appContext!!, payloadFromJson)
85
+ promise?.resolve(true)
80
86
  }
81
87
 
82
88
  fun reportMessageDismissed(
83
89
  message: ReadableMap?,
84
90
  promise: Promise?
85
91
  ) {
86
- message?.let {
87
- val messageMap = message.toMap()
88
- messageMap["nativeJsonPayload"]?.let {
89
- val jsonString = Gson().toJson(it)
90
- val payloadFromJson = Gson().fromJson(jsonString, CampaignPayload::class.java)
91
- HydraInAppUIHelper.getInstance()
92
- .reportInAppMessageDismissed(HydraCore.appContext!!, payloadFromJson)
93
- promise?.resolve(true)
94
- }
92
+ if (message == null) {
93
+ Logger.e("HydraInAppMessages.reportMessageDismissed", "message is null, cannot report dismissal")
94
+ promise?.reject("Error", "message is null")
95
+ return
96
+ }
97
+ val messageMap = message.toMap()
98
+ val nativeJsonPayload = messageMap["nativeJsonPayload"]
99
+ if (nativeJsonPayload == null) {
100
+ Logger.e("HydraInAppMessages.reportMessageDismissed", "nativeJsonPayload missing in message, cannot report dismissal")
101
+ promise?.reject("Error", "nativeJsonPayload missing in message")
102
+ return
95
103
  }
104
+ val jsonString = Gson().toJson(nativeJsonPayload)
105
+ val payloadFromJson = Gson().fromJson(jsonString, CampaignPayload::class.java)
106
+ HydraInAppUIHelper.getInstance()
107
+ .reportInAppMessageDismissed(HydraCore.appContext!!, payloadFromJson)
108
+ promise?.resolve(true)
96
109
  }
97
110
 
98
111
  fun reportMessageRating(
@@ -100,16 +113,23 @@ object HydraInAppMessages {
100
113
  rating: Double,
101
114
  promise: Promise?
102
115
  ) {
103
- message?.let {
104
- val messageMap = message.toMap()
105
- messageMap["nativeJsonPayload"]?.let {
106
- val jsonString = Gson().toJson(it)
107
- val payloadFromJson = Gson().fromJson(jsonString, CampaignPayload::class.java)
108
- HydraInAppUIHelper.getInstance()
109
- .reportInAppMessageRating(HydraCore.appContext!!, payloadFromJson, rating.toInt())
110
- promise?.resolve(true)
111
- }
116
+ if (message == null) {
117
+ Logger.e("HydraInAppMessages.reportMessageRating", "message is null, cannot report rating")
118
+ promise?.reject("Error", "message is null")
119
+ return
120
+ }
121
+ val messageMap = message.toMap()
122
+ val nativeJsonPayload = messageMap["nativeJsonPayload"]
123
+ if (nativeJsonPayload == null) {
124
+ Logger.e("HydraInAppMessages.reportMessageRating", "nativeJsonPayload missing in message, cannot report rating")
125
+ promise?.reject("Error", "nativeJsonPayload missing in message")
126
+ return
112
127
  }
128
+ val jsonString = Gson().toJson(nativeJsonPayload)
129
+ val payloadFromJson = Gson().fromJson(jsonString, CampaignPayload::class.java)
130
+ HydraInAppUIHelper.getInstance()
131
+ .reportInAppMessageRating(HydraCore.appContext!!, payloadFromJson, rating.toInt())
132
+ promise?.resolve(true)
113
133
  }
114
134
 
115
135
  fun reportMessageFeedback(
@@ -118,19 +138,26 @@ object HydraInAppMessages {
118
138
  feedbackOptions: ReadableArray?,
119
139
  promise: Promise?
120
140
  ) {
121
- message?.let {
122
- val messageMap = message.toMap()
123
- messageMap["nativeJsonPayload"]?.let {
124
- val jsonString = Gson().toJson(it)
125
- val payloadFromJson = Gson().fromJson(jsonString, CampaignPayload::class.java)
126
- val feedbackOptionsList = feedbackOptions?.toList()?.mapNotNull { option -> option as? Int } ?: listOf()
127
- HydraInAppUIHelper.getInstance().reportInAppMessageFeedback(
128
- HydraCore.appContext!!,
129
- payloadFromJson,
130
- feedbackOptionsList
131
- )
132
- promise?.resolve(true)
133
- }
141
+ if (message == null) {
142
+ Logger.e("HydraInAppMessages.reportMessageFeedback", "message is null, cannot report feedback")
143
+ promise?.reject("Error", "message is null")
144
+ return
145
+ }
146
+ val messageMap = message.toMap()
147
+ val nativeJsonPayload = messageMap["nativeJsonPayload"]
148
+ if (nativeJsonPayload == null) {
149
+ Logger.e("HydraInAppMessages.reportMessageFeedback", "nativeJsonPayload missing in message, cannot report feedback")
150
+ promise?.reject("Error", "nativeJsonPayload missing in message")
151
+ return
134
152
  }
153
+ val jsonString = Gson().toJson(nativeJsonPayload)
154
+ val payloadFromJson = Gson().fromJson(jsonString, CampaignPayload::class.java)
155
+ val feedbackOptionsList = feedbackOptions?.toList()?.mapNotNull { option -> option as? Int } ?: listOf()
156
+ HydraInAppUIHelper.getInstance().reportInAppMessageFeedback(
157
+ HydraCore.appContext!!,
158
+ payloadFromJson,
159
+ feedbackOptionsList
160
+ )
161
+ promise?.resolve(true)
135
162
  }
136
163
  }
@@ -43,7 +43,7 @@ object HydraInbox {
43
43
  Logger.d("HydraInbox.markInboxMessageAsRead", "Inbox message marked read successfully")
44
44
  return
45
45
  }
46
- Logger.d("HydraInbox.markInboxMessageAsRead", "Invalid inbox message details")
46
+ Logger.e("HydraInbox.markInboxMessageAsRead", "Invalid inbox message details, input message = $inboxMessage")
47
47
  promise?.resolve(false)
48
48
  }
49
49
 
@@ -56,7 +56,7 @@ object HydraInbox {
56
56
  Logger.d("HydraInbox.deleteInboxMessage", "Inbox message deleted successfully")
57
57
  return
58
58
  }
59
- Logger.d("HydraInbox.deleteInboxMessage", "Invalid inbox message details")
59
+ Logger.e("HydraInbox.deleteInboxMessage", "Invalid inbox message details, input message = $inboxMessage")
60
60
  promise?.resolve(false)
61
61
  }
62
62
 
@@ -6,10 +6,12 @@ import android.app.NotificationManager
6
6
  import android.app.PendingIntent
7
7
  import android.content.Context
8
8
  import android.content.Intent
9
+ import android.content.pm.PackageManager
9
10
  import android.net.Uri
10
11
  import android.os.Build
11
12
  import com.hydralib.delegates.NonHydraNotificationActivity
12
13
  import androidx.core.app.NotificationCompat
14
+ import androidx.core.content.ContextCompat
13
15
  import com.capillary.hydra.android.sdk.utils.Logger
14
16
  import com.capillary.hydra.firebase.HydraFirebaseAPI
15
17
  import com.capillary.hydra.firebase.listener.NonHydraPushListener
@@ -155,6 +157,32 @@ object HydraPushNotification {
155
157
  return
156
158
  }
157
159
 
160
+ // Already granted: answer from the OS state instead of asking again. requestPushPermission
161
+ // starts PermissionActivity unconditionally, and that activity lands in the host app's task
162
+ // even when it has nothing to show and finishes immediately. During a notification-click
163
+ // cold start that pulls the app back on top of the browser we just handed an EXTERNAL CTA
164
+ // to, so the external link looks like it did nothing.
165
+ //
166
+ // Placed before the listener registration on purpose: the promise below is resolved from
167
+ // NotificationPermissionListener, which only fires in response to requestPushPermission, so
168
+ // returning after registering would leak a listener that never fires and hang the promise.
169
+ // Spelled out rather than Manifest.permission.POST_NOTIFICATIONS: that constant is API 33,
170
+ // and this library's compileSdkVersion falls back to 31 (gradle.properties) for any host app
171
+ // whose rootProject.ext does not override it, so the symbol would not resolve there. The
172
+ // string is the platform's own value and is stable.
173
+ val context = HydraCore.appContext
174
+ if (context != null &&
175
+ ContextCompat.checkSelfPermission(context, "android.permission.POST_NOTIFICATIONS")
176
+ == PackageManager.PERMISSION_GRANTED
177
+ ) {
178
+ Logger.d(
179
+ "HydraPushNotification.requestPermission",
180
+ "POST_NOTIFICATIONS already granted, resolving without starting PermissionActivity"
181
+ )
182
+ promise?.resolve(true)
183
+ return
184
+ }
185
+
158
186
  var listener: NotificationPermissionListener? = null
159
187
  listener = NotificationPermissionListener { isGranted ->
160
188
  Logger.d(
@@ -190,7 +218,7 @@ object HydraPushNotification {
190
218
 
191
219
  fun getFCMToken(promise: Promise?) {
192
220
  if (fcmToken == null) {
193
- Logger.d("HydraPushNotification.getFCMToken", "Token is not available, try later")
221
+ Logger.e("HydraPushNotification.getFCMToken", "Token is not available, try later")
194
222
  promise?.reject("TokenNotSet", "Token is not available, try later")
195
223
  return
196
224
  }
@@ -121,6 +121,22 @@ class HydraLibModule(reactContext: ReactApplicationContext) :
121
121
  HydraCore.setCapToken(token, promise)
122
122
  }
123
123
 
124
+ override fun setGamificationSuppressed(suppressed: Boolean, promise: Promise?) {
125
+ HydraCore.setGamificationSuppressed(suppressed, promise)
126
+ }
127
+
128
+ override fun isGamificationSuppressed(promise: Promise?) {
129
+ HydraCore.isGamificationSuppressed(promise)
130
+ }
131
+
132
+ override fun setThemeMode(mode: String, promise: Promise?) {
133
+ HydraCore.setThemeMode(mode, promise)
134
+ }
135
+
136
+ override fun getThemeMode(promise: Promise?) {
137
+ HydraCore.getThemeMode(promise)
138
+ }
139
+
124
140
  override fun openExternalUrl(url: String, promise: Promise?) {
125
141
  HydraPushNotification.openExternalUrl(url, promise)
126
142
  }
@@ -141,6 +141,26 @@ class HydraLibModule internal constructor(context: ReactApplicationContext) :
141
141
  HydraCore.setCapToken(token, promise)
142
142
  }
143
143
 
144
+ @ReactMethod(isBlockingSynchronousMethod = false)
145
+ fun setGamificationSuppressed(suppressed: Boolean, promise: Promise?) {
146
+ HydraCore.setGamificationSuppressed(suppressed, promise)
147
+ }
148
+
149
+ @ReactMethod(isBlockingSynchronousMethod = false)
150
+ fun isGamificationSuppressed(promise: Promise?) {
151
+ HydraCore.isGamificationSuppressed(promise)
152
+ }
153
+
154
+ @ReactMethod(isBlockingSynchronousMethod = false)
155
+ fun setThemeMode(mode: String, promise: Promise?) {
156
+ HydraCore.setThemeMode(mode, promise)
157
+ }
158
+
159
+ @ReactMethod(isBlockingSynchronousMethod = false)
160
+ fun getThemeMode(promise: Promise?) {
161
+ HydraCore.getThemeMode(promise)
162
+ }
163
+
144
164
  @ReactMethod(isBlockingSynchronousMethod = false)
145
165
  fun openExternalUrl(url: String, promise: Promise?) {
146
166
  HydraPushNotification.openExternalUrl(url, promise)
@@ -32,6 +32,14 @@ import HydraCore
32
32
  }
33
33
 
34
34
  fileprivate func setupNavigationController(_ application: UIApplication) {
35
+ // Wrapping the app's rootViewController in a UINavigationController is only
36
+ // needed to push the Hydra NotificationCenter (see HydraInboxUI). Apps that
37
+ // don't enable InboxUI keep their own root view controller untouched.
38
+ guard HydraSDKCore.isInboxUIEnabled() else {
39
+ Logger.info("HydraApplicationDelegate: setupNavigationController: enable_inbox_ui is false, skipping navigation controller setup")
40
+ return
41
+ }
42
+
35
43
  //Create and set NavigationController as rootController, will be required to show NotificationCenter
36
44
  let storyboard = UIStoryboard(name: Bundle.main.infoDictionary?["UILaunchStoryboardName"] as? String ?? "LaunchScreen", bundle: nil)
37
45
  let viewController = storyboard.instantiateInitialViewController()
@@ -25,4 +25,6 @@ enum HydraEventType : String, CaseIterable{
25
25
  case ON_VISITOR_SESSION_READY = "onVisitorSessionReady"
26
26
  case ON_USER_STATE_CHANGED = "onUserStateChanged"
27
27
  case ON_GAMIFICATION_VIEW_HEIGHT_CHANGED = "onGamificationViewHeightChanged"
28
+ case ON_GAMIFICATION_ANALYTICS_EVENT = "gamificationAnalyticsEvent"
29
+ case ON_GAMIFICATION_DEEPLINK = "gamificationDeeplink"
28
30
  }
@@ -2,10 +2,10 @@
2
2
 
3
3
  #import "GamificationBannerViewComponentView.h"
4
4
 
5
- #import "../generated/RNHydraLibSpec/ComponentDescriptors.h"
6
- #import "../generated/RNHydraLibSpec/EventEmitters.h"
7
- #import "../generated/RNHydraLibSpec/Props.h"
8
- #import "../generated/RNHydraLibSpec/RCTComponentViewHelpers.h"
5
+ #import <react/renderer/components/RNHydraLibSpec/ComponentDescriptors.h>
6
+ #import <react/renderer/components/RNHydraLibSpec/EventEmitters.h>
7
+ #import <react/renderer/components/RNHydraLibSpec/Props.h>
8
+ #import <react/renderer/components/RNHydraLibSpec/RCTComponentViewHelpers.h>
9
9
  #import "RCTFabricComponentsPlugins.h"
10
10
  #import <React/RCTBridgeModule.h>
11
11
  #import <React/RCTComponentViewFactory.h>
@@ -53,12 +53,37 @@ using namespace facebook::react;
53
53
  }
54
54
 
55
55
  - (void)gamificationDataReloaded {
56
+ // Always dispatch to main: the SDK may post this from a background queue,
57
+ // and createGamificationBannerView is a UIKit/MainActor call.
56
58
  dispatch_async(dispatch_get_main_queue(), ^{
59
+ // Fabric keeps unmounted component views alive in its recycle pool, and a pooled view is
60
+ // still a registered observer here. It must NOT rebuild: every successful
61
+ // createGamificationBannerView records an impression against the entry point's display count
62
+ // (conditions.showCount.count), so an off-screen pooled view would silently burn the cap and
63
+ // the banner would vanish for real users far sooner than configured. A mounted view always
64
+ // has a superview; a pooled one never does (Fabric asserts this when enqueueing).
65
+ if (self.superview == nil) {
66
+ return;
67
+ }
57
68
  self->_needsBannerView = YES;
58
69
  [self attachBannerViewIfPossible];
59
70
  });
60
71
  }
61
72
 
73
+ // Fabric recycles component views. RCTViewComponentView's own prepareForRecycle resets the event
74
+ // emitter and react subviews but NOT `contentView`, and setContentView: re-adds it to the container
75
+ // view — so without this a recycled instance keeps the previous mount's banner view (a live
76
+ // WKWebView showing an already-rendered campaign). updateProps would then see `contentView != nil`
77
+ // with an unchanged bannerId and never ask the SDK again, bypassing the frequency-cap/visibility
78
+ // check entirely and displaying stale content the SDK has decided to hide.
79
+ - (void)prepareForRecycle {
80
+ self.contentView = nil;
81
+ _bannerId = nil;
82
+ _needsBannerView = NO;
83
+ _retryPending = NO;
84
+ [super prepareForRecycle];
85
+ }
86
+
62
87
  - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps {
63
88
  const auto &newProps = *std::static_pointer_cast<const GamificationBannerViewProps>(props);
64
89
 
@@ -87,7 +112,11 @@ using namespace facebook::react;
87
112
  if (!_needsBannerView || _bannerId.length == 0) {
88
113
  return;
89
114
  }
90
- if (CGRectIsEmpty(self.bounds)) {
115
+ // Only WIDTH has to be laid out: the SDK ignores our bounds entirely (it measures the window)
116
+ // and seeds the real height back through onHeight. Requiring a non-empty rect would wedge a
117
+ // surface that has legitimately collapsed to height 0 — it could never rebuild when fresh data
118
+ // arrives, because its own bounds would stay empty forever.
119
+ if (self.bounds.size.width <= 0) {
91
120
  return;
92
121
  }
93
122
 
@@ -99,7 +128,15 @@ using namespace facebook::react;
99
128
  _needsBannerView = NO;
100
129
  _retryPending = NO;
101
130
  } else {
102
- NSLog(@"[GamificationBanner] SDK returned nil — scheduling long fallback");
131
+ NSLog(@"[GamificationBanner] SDK returned nil — collapsing and scheduling long fallback");
132
+ // nil means "do not render this banner now" — hidden, frequency-capped, or no resolved URL.
133
+ // Drop anything already attached so a previously loaded webview can't outlive that decision.
134
+ // The SDK has already pushed height 0 through onHeight, so the JS side collapses with us.
135
+ self.contentView = nil;
136
+ // Don't retry aggressively: the SDK may have entry points loaded but
137
+ // reward/program data not yet ready. The HydraGamificationDataReloaded
138
+ // notification fires when everything is ready and will call us again.
139
+ // This 5-second fallback only fires if that notification is missed.
103
140
  [self scheduleFallbackRetry];
104
141
  }
105
142
  }
@@ -2,10 +2,10 @@
2
2
 
3
3
  #import "GamificationEmbedViewComponentView.h"
4
4
 
5
- #import "../generated/RNHydraLibSpec/ComponentDescriptors.h"
6
- #import "../generated/RNHydraLibSpec/EventEmitters.h"
7
- #import "../generated/RNHydraLibSpec/Props.h"
8
- #import "../generated/RNHydraLibSpec/RCTComponentViewHelpers.h"
5
+ #import <react/renderer/components/RNHydraLibSpec/ComponentDescriptors.h>
6
+ #import <react/renderer/components/RNHydraLibSpec/EventEmitters.h>
7
+ #import <react/renderer/components/RNHydraLibSpec/Props.h>
8
+ #import <react/renderer/components/RNHydraLibSpec/RCTComponentViewHelpers.h>
9
9
  #import "RCTFabricComponentsPlugins.h"
10
10
  #import <React/RCTBridgeModule.h>
11
11
  #import <React/RCTComponentViewFactory.h>
@@ -56,11 +56,34 @@ using namespace facebook::react;
56
56
  // Always dispatch to main: the SDK may post this from a background queue,
57
57
  // and createGamificationEmbedView is a UIKit/MainActor call.
58
58
  dispatch_async(dispatch_get_main_queue(), ^{
59
+ // Fabric keeps unmounted component views alive in its recycle pool, and a pooled view is
60
+ // still a registered observer here. It must NOT rebuild: every successful
61
+ // createGamificationEmbedView records an impression against the entry point's display count
62
+ // (conditions.showCount.count), so an off-screen pooled view would silently burn the cap and
63
+ // the embed would vanish for real users far sooner than configured. A mounted view always
64
+ // has a superview; a pooled one never does (Fabric asserts this when enqueueing).
65
+ if (self.superview == nil) {
66
+ return;
67
+ }
59
68
  self->_needsEmbedView = YES;
60
69
  [self attachEmbedViewIfPossible];
61
70
  });
62
71
  }
63
72
 
73
+ // Fabric recycles component views. RCTViewComponentView's own prepareForRecycle resets the event
74
+ // emitter and react subviews but NOT `contentView`, and setContentView: re-adds it to the container
75
+ // view — so without this a recycled instance keeps the previous mount's EmbedView (a live WKWebView
76
+ // showing an already-rendered campaign). updateProps would then see `contentView != nil` with an
77
+ // unchanged embedId and never ask the SDK again, bypassing the frequency-cap/visibility check
78
+ // entirely and displaying stale content the SDK has decided to hide.
79
+ - (void)prepareForRecycle {
80
+ self.contentView = nil;
81
+ _embedId = nil;
82
+ _needsEmbedView = NO;
83
+ _retryPending = NO;
84
+ [super prepareForRecycle];
85
+ }
86
+
64
87
  - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps {
65
88
  const auto &newProps = *std::static_pointer_cast<const GamificationEmbedViewProps>(props);
66
89
 
@@ -89,7 +112,11 @@ using namespace facebook::react;
89
112
  if (!_needsEmbedView || _embedId.length == 0) {
90
113
  return;
91
114
  }
92
- if (CGRectIsEmpty(self.bounds)) {
115
+ // Only WIDTH has to be laid out: the SDK ignores our bounds entirely (it measures the window)
116
+ // and seeds the real height back through onHeight. Requiring a non-empty rect would wedge a
117
+ // surface that has legitimately collapsed to height 0 — it could never rebuild when fresh data
118
+ // arrives, because its own bounds would stay empty forever.
119
+ if (self.bounds.size.width <= 0) {
93
120
  return;
94
121
  }
95
122
 
@@ -101,7 +128,11 @@ using namespace facebook::react;
101
128
  _needsEmbedView = NO;
102
129
  _retryPending = NO;
103
130
  } else {
104
- NSLog(@"[GamificationEmbed] SDK returned nil — scheduling long fallback");
131
+ NSLog(@"[GamificationEmbed] SDK returned nil — collapsing and scheduling long fallback");
132
+ // nil means "do not render this embed now" — hidden, frequency-capped, or no resolved URL.
133
+ // Drop anything already attached so a previously loaded webview can't outlive that decision.
134
+ // The SDK has already pushed height 0 through onHeight, so the JS side collapses with us.
135
+ self.contentView = nil;
105
136
  // Don't retry aggressively: the SDK may have entry points loaded but
106
137
  // reward/program data not yet ready. The HydraGamificationDataReloaded
107
138
  // notification fires when everything is ready and will call us again.
@@ -2,10 +2,10 @@
2
2
 
3
3
  #import "GamificationLoyaltyViewComponentView.h"
4
4
 
5
- #import "../generated/RNHydraLibSpec/ComponentDescriptors.h"
6
- #import "../generated/RNHydraLibSpec/EventEmitters.h"
7
- #import "../generated/RNHydraLibSpec/Props.h"
8
- #import "../generated/RNHydraLibSpec/RCTComponentViewHelpers.h"
5
+ #import <react/renderer/components/RNHydraLibSpec/ComponentDescriptors.h>
6
+ #import <react/renderer/components/RNHydraLibSpec/EventEmitters.h>
7
+ #import <react/renderer/components/RNHydraLibSpec/Props.h>
8
+ #import <react/renderer/components/RNHydraLibSpec/RCTComponentViewHelpers.h>
9
9
  #import "RCTFabricComponentsPlugins.h"
10
10
  #import <React/RCTBridgeModule.h>
11
11
  #import <React/RCTComponentViewFactory.h>
@@ -53,12 +53,37 @@ using namespace facebook::react;
53
53
  }
54
54
 
55
55
  - (void)gamificationDataReloaded {
56
+ // Always dispatch to main: the SDK may post this from a background queue,
57
+ // and createGamificationLoyaltyView is a UIKit/MainActor call.
56
58
  dispatch_async(dispatch_get_main_queue(), ^{
59
+ // Fabric keeps unmounted component views alive in its recycle pool, and a pooled view is
60
+ // still a registered observer here. It must NOT rebuild: every successful
61
+ // createGamificationLoyaltyView records an impression against the entry point's display count
62
+ // (conditions.showCount.count), so an off-screen pooled view would silently burn the cap and
63
+ // the widget would vanish for real users far sooner than configured. A mounted view always
64
+ // has a superview; a pooled one never does (Fabric asserts this when enqueueing).
65
+ if (self.superview == nil) {
66
+ return;
67
+ }
57
68
  self->_needsLoyaltyView = YES;
58
69
  [self attachLoyaltyViewIfPossible];
59
70
  });
60
71
  }
61
72
 
73
+ // Fabric recycles component views. RCTViewComponentView's own prepareForRecycle resets the event
74
+ // emitter and react subviews but NOT `contentView`, and setContentView: re-adds it to the container
75
+ // view — so without this a recycled instance keeps the previous mount's loyalty view (a live
76
+ // WKWebView showing an already-rendered campaign). updateProps would then see `contentView != nil`
77
+ // with an unchanged loyaltyId and never ask the SDK again, bypassing the frequency-cap/visibility
78
+ // check entirely and displaying stale content the SDK has decided to hide.
79
+ - (void)prepareForRecycle {
80
+ self.contentView = nil;
81
+ _loyaltyId = nil;
82
+ _needsLoyaltyView = NO;
83
+ _retryPending = NO;
84
+ [super prepareForRecycle];
85
+ }
86
+
62
87
  - (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps {
63
88
  const auto &newProps = *std::static_pointer_cast<const GamificationLoyaltyViewProps>(props);
64
89
 
@@ -85,16 +110,31 @@ using namespace facebook::react;
85
110
  if (!_needsLoyaltyView || _loyaltyId.length == 0) {
86
111
  return;
87
112
  }
88
- if (CGRectIsEmpty(self.bounds)) {
113
+ // Only WIDTH has to be laid out: the SDK ignores our bounds entirely (it measures the window)
114
+ // and seeds the real height back through onHeight. Requiring a non-empty rect would wedge a
115
+ // surface that has legitimately collapsed to height 0 — it could never rebuild when fresh data
116
+ // arrives, because its own bounds would stay empty forever.
117
+ if (self.bounds.size.width <= 0) {
89
118
  return;
90
119
  }
91
120
 
121
+ NSLog(@"[GamificationLoyalty] trying createGamificationLoyaltyView(loyaltyId=%@)", _loyaltyId);
92
122
  UIView *loyaltyView = [[HydraSDKCore shared] createGamificationLoyaltyViewWithLoyaltyId:_loyaltyId];
93
123
  if (loyaltyView) {
124
+ NSLog(@"[GamificationLoyalty] loyalty view created OK");
94
125
  self.contentView = loyaltyView;
95
126
  _needsLoyaltyView = NO;
96
127
  _retryPending = NO;
97
128
  } else {
129
+ NSLog(@"[GamificationLoyalty] SDK returned nil — collapsing and scheduling long fallback");
130
+ // nil means "do not render this now" — hidden, frequency-capped, or no resolved URL.
131
+ // Drop anything already attached so a previously loaded webview can't outlive that decision.
132
+ // The SDK has already pushed height 0 through onHeight, so the JS side collapses with us.
133
+ self.contentView = nil;
134
+ // Don't retry aggressively: the SDK may have entry points loaded but
135
+ // reward/program data not yet ready. The HydraGamificationDataReloaded
136
+ // notification fires when everything is ready and will call us again.
137
+ // This 5-second fallback only fires if that notification is missed.
98
138
  [self scheduleFallbackRetry];
99
139
  }
100
140
  }
package/ios/HydraLib.h CHANGED
@@ -1,11 +1,10 @@
1
1
  #import <React/RCTEventEmitter.h>
2
2
 
3
3
  #ifdef RCT_NEW_ARCH_ENABLED
4
- #ifdef RN_VERSION_BELOW_76
4
+ // Codegen output is generated by the consuming app's own build against its own
5
+ // React Native version, and exposed on the header search path by the
6
+ // ReactCodegen pod - this package no longer ships a pre-generated copy.
5
7
  #import <RNHydraLibSpec/RNHydraLibSpec.h>
6
- #else
7
- #import "generated/RNHydraLibSpec/RNHydraLibSpec.h"
8
- #endif
9
8
  @interface HydraLib : RCTEventEmitter <NativeHydraLibSpec>
10
9
  #else
11
10
  #import <React/RCTBridgeModule.h>
package/ios/HydraLib.mm CHANGED
@@ -194,6 +194,22 @@ RCT_EXPORT_METHOD(reportMessageRating:(nonnull NSDictionary *)message rating:(do
194
194
  [[HydraSDKCore shared] setCapToken:token resolve:resolve reject:reject];
195
195
  }
196
196
 
197
+ - (void)setGamificationSuppressed:(BOOL)suppressed resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
198
+ [[HydraSDKCore shared] setGamificationSuppressed:suppressed resolve:resolve reject:reject];
199
+ }
200
+
201
+ - (void)isGamificationSuppressed:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
202
+ [[HydraSDKCore shared] isGamificationSuppressed:resolve reject:reject];
203
+ }
204
+
205
+ - (void)setThemeMode:(nonnull NSString *)mode resolve:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
206
+ [[HydraSDKCore shared] setThemeMode:mode resolve:resolve reject:reject];
207
+ }
208
+
209
+ - (void)getThemeMode:(nonnull RCTPromiseResolveBlock)resolve reject:(nonnull RCTPromiseRejectBlock)reject {
210
+ [[HydraSDKCore shared] getThemeMode:resolve reject:reject];
211
+ }
212
+
197
213
 
198
214
 
199
215
  #ifdef RCT_NEW_ARCH_ENABLED
@@ -60,22 +60,49 @@ import React
60
60
  // }
61
61
  }
62
62
 
63
+ // NOTE: these four methods do not yet call into a corresponding HydraCore/HydraInAppMessage
64
+ // report API (unlike the Android side, which calls HydraInAppUIHelper...reportInAppMessage*).
65
+ // The logging/resolve/reject below only makes failures visible and stops the RN promise
66
+ // from hanging forever — it does not report the click/dismiss/rating/feedback to the SDK.
67
+
63
68
  @objc public func reportMessageClicked(_ message: [String: Any]?, resolve : RCTPromiseResolveBlock?, reject : RCTPromiseRejectBlock?) -> Void{
64
- if let payload = message?["nativeJsonPayload"] as? [String: Any]{
65
- Logger.debug("HydraInAppMessages.reportMessageClicked payload = \(payload)")
69
+ guard let payload = message?["nativeJsonPayload"] as? [String: Any] else {
70
+ Logger.error("HydraInAppMessages: reportMessageClicked: message is nil or missing nativeJsonPayload")
71
+ reject?("Error", "message is nil or missing nativeJsonPayload", nil)
72
+ return
66
73
  }
74
+ Logger.debug("HydraInAppMessages.reportMessageClicked payload = \(payload)")
75
+ resolve?(true)
67
76
  }
68
-
77
+
69
78
  @objc public func reportMessageDismissed(_ message: [String: Any]?, resolve : RCTPromiseResolveBlock?, reject : RCTPromiseRejectBlock?) -> Void{
70
-
79
+ guard let payload = message?["nativeJsonPayload"] as? [String: Any] else {
80
+ Logger.error("HydraInAppMessages: reportMessageDismissed: message is nil or missing nativeJsonPayload")
81
+ reject?("Error", "message is nil or missing nativeJsonPayload", nil)
82
+ return
83
+ }
84
+ Logger.debug("HydraInAppMessages.reportMessageDismissed payload = \(payload)")
85
+ resolve?(true)
71
86
  }
72
-
87
+
73
88
  @objc public func reportMessageRating(_ message: [String: Any]?, rating: Double, resolve : RCTPromiseResolveBlock?, reject : RCTPromiseRejectBlock?) -> Void{
74
-
89
+ guard let payload = message?["nativeJsonPayload"] as? [String: Any] else {
90
+ Logger.error("HydraInAppMessages: reportMessageRating: message is nil or missing nativeJsonPayload")
91
+ reject?("Error", "message is nil or missing nativeJsonPayload", nil)
92
+ return
93
+ }
94
+ Logger.debug("HydraInAppMessages.reportMessageRating payload = \(payload), rating = \(rating)")
95
+ resolve?(true)
75
96
  }
76
-
97
+
77
98
  @objc public func reportMessageFeedback(_ message: [String: Any]?, feedback: String, feedbackOptions: [String], resolve : RCTPromiseResolveBlock?, reject : RCTPromiseRejectBlock?) -> Void{
78
-
99
+ guard let payload = message?["nativeJsonPayload"] as? [String: Any] else {
100
+ Logger.error("HydraInAppMessages: reportMessageFeedback: message is nil or missing nativeJsonPayload")
101
+ reject?("Error", "message is nil or missing nativeJsonPayload", nil)
102
+ return
103
+ }
104
+ Logger.debug("HydraInAppMessages.reportMessageFeedback payload = \(payload), feedback = \(feedback), feedbackOptions = \(feedbackOptions)")
105
+ resolve?(true)
79
106
  }
80
107
  }
81
108