@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
@@ -138,12 +138,10 @@ android {
138
138
  java.srcDirs += [
139
139
  "src/newarch"
140
140
  ]
141
- if(reactNativeMinorVersion() >= 76){
142
- java.srcDirs += [
143
- "generated/java",
144
- "generated/jni"
145
- ]
146
- }
141
+ // Codegen output is not shipped with this package - the
142
+ // com.facebook.react plugin applied above generates it into
143
+ // build/generated/source/codegen and adds it to the source set
144
+ // itself, using the consuming app's own React Native version.
147
145
  }
148
146
  else{
149
147
  java.srcDirs += ["src/oldarch"]
@@ -166,6 +164,7 @@ android {
166
164
  }
167
165
 
168
166
  repositories {
167
+ mavenLocal()
169
168
  mavenCentral()
170
169
  google()
171
170
  }
@@ -180,14 +179,14 @@ dependencies {
180
179
  //noinspection GradleDependency
181
180
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
182
181
 
183
- implementation "com.capillarytech:hydra-core:1.9.22-beta05"
184
- implementation "com.capillarytech:hydra-pushbase:1.9.22-beta05"
185
- implementation "com.capillarytech:hydra-firebase:1.9.22-beta05"
186
- implementation "com.capillarytech:hydra-richnotification:1.9.22-beta05"
182
+ implementation "com.capillarytech:hydra-core:1.9.22-beta09"
183
+ implementation "com.capillarytech:hydra-pushbase:1.9.22-beta09"
184
+ implementation "com.capillarytech:hydra-firebase:1.9.22-beta09"
185
+ implementation "com.capillarytech:hydra-richnotification:1.9.22-beta09"
187
186
 
188
- implementation "com.capillarytech:hydra-inbox-core:1.9.22-beta05"
189
- implementation "com.capillarytech:hydra-inbox-ui:1.9.22-beta05"
190
- implementation "com.capillarytech:hydra-inapp:1.9.22-beta05"
187
+ implementation "com.capillarytech:hydra-inbox-core:1.9.22-beta09"
188
+ implementation "com.capillarytech:hydra-inbox-ui:1.9.22-beta09"
189
+ implementation "com.capillarytech:hydra-inapp:1.9.22-beta09"
191
190
  implementation "com.google.firebase:firebase-messaging-ktx:24.1.0"
192
191
 
193
192
  implementation "com.google.code.gson:gson:2.11.0"
@@ -5,6 +5,7 @@ import androidx.annotation.RestrictTo
5
5
  import androidx.appcompat.app.AppCompatActivity
6
6
  import com.capillary.hydra.android.sdk.utils.Logger
7
7
  import com.google.gson.Gson
8
+ import com.google.gson.JsonSyntaxException
8
9
  import com.google.gson.reflect.TypeToken
9
10
  import com.hydralib.eventemitter.HydraEventEmitter
10
11
  import com.hydralib.eventemitter.HydraEventType
@@ -27,17 +28,28 @@ class NonHydraNotificationActivity : AppCompatActivity() {
27
28
  val remoteMessage = intent.getStringExtra(EXTRA_REMOTE_MESSAGE)
28
29
 
29
30
  Logger.d(TAG, "Remote Message: $remoteMessage")
30
- val gson = Gson()
31
- val type = object : TypeToken<Map<String, Any>>() {}.type
32
- val remoteMessageMap = gson.fromJson<Map<String, Any>>(remoteMessage, type)
33
31
 
34
- // Emit event to React Native
35
- HydraEventEmitter.sendEvent(
36
- HydraEventType.ON_NON_HYDRA_NOTIFICATION_CLICKED,
37
- remoteMessageMap
38
- )
39
-
40
- Logger.d(TAG, "Event emitted to React Native: ON_NON_HYDRA_NOTIFICATION_CLICKED")
32
+ if (remoteMessage.isNullOrEmpty()) {
33
+ Logger.e(TAG, "EXTRA_REMOTE_MESSAGE missing from intent, cannot emit click event")
34
+ finish()
35
+ return
36
+ }
37
+
38
+ try {
39
+ val gson = Gson()
40
+ val type = object : TypeToken<Map<String, Any>>() {}.type
41
+ val remoteMessageMap = gson.fromJson<Map<String, Any>>(remoteMessage, type)
42
+
43
+ // Emit event to React Native
44
+ HydraEventEmitter.sendEvent(
45
+ HydraEventType.ON_NON_HYDRA_NOTIFICATION_CLICKED,
46
+ remoteMessageMap
47
+ )
48
+
49
+ Logger.d(TAG, "Event emitted to React Native: ON_NON_HYDRA_NOTIFICATION_CLICKED")
50
+ } catch (e: JsonSyntaxException) {
51
+ Logger.e(TAG, "Failed to parse remote message JSON: ${e.message}\n${e.stackTraceToString()}")
52
+ }
41
53
  finish()
42
54
  }
43
55
  }
@@ -60,6 +60,11 @@ object HydraEventEmitter {
60
60
  if(callback != null) {
61
61
  eventsWithPendingCallback.add(PendingEvent(body, callback))
62
62
  }
63
+ } else {
64
+ Logger.w(
65
+ "HydraEventEmitter.sendEvent",
66
+ "No active listener for eventType = ${eventType.event}, dropping event. body = $body"
67
+ )
63
68
  }
64
69
  }
65
70
  }
@@ -82,8 +87,23 @@ object HydraEventEmitter {
82
87
  "HydraEventEmitter.addListener",
83
88
  "addListener called for eventName = $eventName"
84
89
  )
85
- val eventType = HydraEventType.from(eventName!!)
86
- if (!activeEventTypes.contains(eventType!!)) {
90
+ if (eventName == null) {
91
+ Logger.e("HydraEventEmitter.addListener", "eventName is null, ignoring addListener call")
92
+ return
93
+ }
94
+ val eventType = try {
95
+ // HydraEventType.from is declared as returning HydraEventType?, but it never actually
96
+ // returns null — it throws IllegalArgumentException for an unmapped name — so the null
97
+ // branch below is unreachable in practice; kept only to satisfy the nullable return type.
98
+ HydraEventType.from(eventName) ?: run {
99
+ Logger.e("HydraEventEmitter.addListener", "Unknown eventName = $eventName, ignoring addListener call")
100
+ return
101
+ }
102
+ } catch (e: IllegalArgumentException) {
103
+ Logger.e("HydraEventEmitter.addListener", "Unknown eventName = $eventName, ignoring addListener call")
104
+ return
105
+ }
106
+ if (!activeEventTypes.contains(eventType)) {
87
107
  Logger.d(
88
108
  "HydraEventEmitter.addListener",
89
109
  "adding event for eventName = $eventName"
@@ -107,13 +127,23 @@ object HydraEventEmitter {
107
127
  "removeListener called for eventName = $eventName"
108
128
  )
109
129
 
110
- if (activeEventTypes.contains(HydraEventType.from(eventName!!))) {
130
+ if (eventName == null) {
131
+ Logger.e("HydraEventEmitter.removeListener", "eventName is null, ignoring removeListener call")
132
+ return
133
+ }
134
+ val eventType = try {
135
+ HydraEventType.from(eventName)
136
+ } catch (e: IllegalArgumentException) {
137
+ Logger.e("HydraEventEmitter.removeListener", "Unknown eventName = $eventName, ignoring removeListener call")
138
+ return
139
+ }
140
+ if (eventType != null && activeEventTypes.contains(eventType)) {
111
141
  Logger.d(
112
142
  "HydraEventEmitter.removeListener",
113
143
  "removing event for eventName = $eventName"
114
144
  )
115
145
 
116
- activeEventTypes.remove(HydraEventType.from(eventName)!!)
146
+ activeEventTypes.remove(eventType)
117
147
  }
118
148
  }
119
149
 
@@ -17,7 +17,9 @@ enum class HydraEventType(val event: String) {
17
17
  ON_LEGACY_FLOW_READY("onLegacyFlowReady"),
18
18
  ON_VISITOR_SESSION_READY("onVisitorSessionReady"),
19
19
  ON_USER_STATE_CHANGED("onUserStateChanged"),
20
- ON_GAMIFICATION_VIEW_HEIGHT_CHANGED("onGamificationViewHeightChanged");
20
+ ON_GAMIFICATION_VIEW_HEIGHT_CHANGED("onGamificationViewHeightChanged"),
21
+ ON_GAMIFICATION_ANALYTICS_EVENT("gamificationAnalyticsEvent"),
22
+ ON_GAMIFICATION_DEEPLINK("gamificationDeeplink");
21
23
 
22
24
  companion object {
23
25
  fun from(eventName: String?): HydraEventType? = when (eventName) {
@@ -38,6 +40,8 @@ enum class HydraEventType(val event: String) {
38
40
  ON_VISITOR_SESSION_READY.event -> ON_VISITOR_SESSION_READY
39
41
  ON_USER_STATE_CHANGED.event -> ON_USER_STATE_CHANGED
40
42
  ON_GAMIFICATION_VIEW_HEIGHT_CHANGED.event -> ON_GAMIFICATION_VIEW_HEIGHT_CHANGED
43
+ ON_GAMIFICATION_ANALYTICS_EVENT.event -> ON_GAMIFICATION_ANALYTICS_EVENT
44
+ ON_GAMIFICATION_DEEPLINK.event -> ON_GAMIFICATION_DEEPLINK
41
45
  else -> throw IllegalArgumentException()
42
46
  }
43
47
  }
@@ -9,6 +9,7 @@ import com.facebook.react.module.annotations.ReactModule
9
9
  import com.facebook.react.uimanager.SimpleViewManager
10
10
  import com.facebook.react.uimanager.ThemedReactContext
11
11
  import com.facebook.react.uimanager.annotations.ReactProp
12
+ import com.capillary.hydra.android.sdk.utils.Logger
12
13
  import com.hydralib.eventemitter.HydraEventEmitter
13
14
  import com.hydralib.eventemitter.HydraEventType
14
15
  import com.hydralib.hydrasdk.HydraCore
@@ -23,11 +24,21 @@ class GamificationBannerViewManager : SimpleViewManager<BannerView>() {
23
24
 
24
25
  @ReactProp(name = "bannerId")
25
26
  fun setBannerId(view: BannerView, bannerId: String?) {
26
- if (bannerId.isNullOrEmpty()) return
27
+ if (bannerId.isNullOrEmpty()) {
28
+ Logger.e("GamificationBannerViewManager.setBannerId", "bannerId prop is null or empty, skipping bind")
29
+ return
30
+ }
31
+ // Guard: don't re-bind if already bound to this bannerId. Without this, every RN
32
+ // re-render (triggered by the height-changed event) calls bindWithLayout again and
33
+ // re-increments the SDK's persistent show-count for this entry point, exhausting its
34
+ // frequency cap in half the real number of screen visits.
35
+ if (view.tag == bannerId) return
36
+ // findEntryPointByBannerId already logs the reason on failure
27
37
  val data = findEntryPointByBannerId(bannerId) ?: return
28
38
 
29
39
  // Defer bind until Yoga has measured and laid out the view
30
40
  if (view.width > 0 && view.height > 0) {
41
+ view.tag = bannerId
31
42
  bindWithLayout(view, data, bannerId)
32
43
  } else {
33
44
  view.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
@@ -37,6 +48,7 @@ class GamificationBannerViewManager : SimpleViewManager<BannerView>() {
37
48
  ) {
38
49
  if (right - left > 0 && bottom - top > 0) {
39
50
  view.removeOnLayoutChangeListener(this)
51
+ view.tag = bannerId
40
52
  bindWithLayout(view, data, bannerId)
41
53
  }
42
54
  }
@@ -57,6 +69,14 @@ class GamificationBannerViewManager : SimpleViewManager<BannerView>() {
57
69
  private fun bindWithLayout(view: BannerView, data: EntryPointData, bannerId: String) {
58
70
  val w = view.width
59
71
  val activity = HydraCore.curReactActivity
72
+ if (activity == null) {
73
+ // Not a fatal bind failure — the banner still renders — but entry point clicks
74
+ // will silently no-op below for the life of this view, so surface it.
75
+ Logger.e(
76
+ "GamificationBannerViewManager.bindWithLayout",
77
+ "curReactActivity is null, entry point clicks will not be handled for bannerId = $bannerId"
78
+ )
79
+ }
60
80
 
61
81
  // Set height to 0 so BannerView.bind() enters SDK-managed path
62
82
  // and calculates height from dashboard container.height (dp)
@@ -110,9 +130,20 @@ class GamificationBannerViewManager : SimpleViewManager<BannerView>() {
110
130
  }
111
131
 
112
132
  private fun findEntryPointByBannerId(bannerId: String): EntryPointData? {
113
- val flowManager = HydraCore.getFlowManager() ?: return null
133
+ val flowManager = HydraCore.getFlowManager()
134
+ if (flowManager == null) {
135
+ Logger.e("GamificationBannerViewManager.findEntryPointByBannerId", "FlowManager is null, cannot resolve bannerId = $bannerId")
136
+ return null
137
+ }
114
138
  val entryPoints = flowManager.getFilteredEntryPoints(null)
115
- return entryPoints.find { it.mobile?.container?.bannerId == bannerId }
139
+ val entryPoint = entryPoints.find { it.mobile?.container?.bannerId == bannerId }
140
+ if (entryPoint == null) {
141
+ Logger.e(
142
+ "GamificationBannerViewManager.findEntryPointByBannerId",
143
+ "No matching entry point for bannerId = $bannerId"
144
+ )
145
+ }
146
+ return entryPoint
116
147
  }
117
148
 
118
149
  companion object {
@@ -8,6 +8,7 @@ import com.facebook.react.module.annotations.ReactModule
8
8
  import com.facebook.react.uimanager.SimpleViewManager
9
9
  import com.facebook.react.uimanager.ThemedReactContext
10
10
  import com.facebook.react.uimanager.annotations.ReactProp
11
+ import com.capillary.hydra.android.sdk.utils.Logger
11
12
  import com.hydralib.eventemitter.HydraEventEmitter
12
13
  import com.hydralib.eventemitter.HydraEventType
13
14
  import com.hydralib.hydrasdk.HydraCore
@@ -29,11 +30,15 @@ class GamificationEmbedViewManager : SimpleViewManager<EmbedWebView>() {
29
30
 
30
31
  @ReactProp(name = "embedId")
31
32
  fun setEmbedId(view: EmbedWebView, embedId: String?) {
32
- if (embedId.isNullOrEmpty()) return
33
+ if (embedId.isNullOrEmpty()) {
34
+ Logger.e("GamificationEmbedViewManager.setEmbedId", "embedId prop is null or empty, skipping bind")
35
+ return
36
+ }
33
37
  // Guard: don't re-bind if already bound to this embedId. Without this, every RN
34
38
  // re-render (triggered by the height-changed event) calls bindWithLayout again and
35
39
  // accumulates an additional OnPreDrawListener, producing an infinite emit loop.
36
40
  if (view.tag == embedId) return
41
+ // findEntryPointByEmbedId already logs the reason on failure
37
42
  val data = findEntryPointByEmbedId(embedId) ?: return
38
43
 
39
44
  // Defer bind until Yoga has measured and laid out the view
@@ -63,7 +68,11 @@ class GamificationEmbedViewManager : SimpleViewManager<EmbedWebView>() {
63
68
  private fun bindWithLayout(view: EmbedWebView, data: EntryPointData, embedId: String) {
64
69
  val w = view.width
65
70
  val h = view.height
66
- val activity = HydraCore.curReactActivity ?: return
71
+ val activity = HydraCore.curReactActivity
72
+ if (activity == null) {
73
+ Logger.e("GamificationEmbedViewManager.bindWithLayout", "curReactActivity is null, cannot bind embed view")
74
+ return
75
+ }
67
76
 
68
77
  // Sync layoutParams with Yoga's actual pixel dimensions
69
78
  // so SDK sees a positive height and takes host-controlled path
@@ -136,12 +145,24 @@ class GamificationEmbedViewManager : SimpleViewManager<EmbedWebView>() {
136
145
  }
137
146
 
138
147
  private fun findEntryPointByEmbedId(embedId: String): EntryPointData? {
139
- val flowManager = HydraCore.getFlowManager() ?: return null
148
+ val flowManager = HydraCore.getFlowManager()
149
+ if (flowManager == null) {
150
+ Logger.e("GamificationEmbedViewManager.findEntryPointByEmbedId", "FlowManager is null, cannot resolve embedId = $embedId")
151
+ return null
152
+ }
140
153
  val entryPoints = flowManager.getFilteredEntryPoints(null)
141
- return entryPoints.find { it.mobile?.container?.bannerId == embedId }
154
+ val entryPoint = entryPoints.find { it.mobile?.container?.bannerId == embedId }
155
+ if (entryPoint == null) {
156
+ Logger.e(
157
+ "GamificationEmbedViewManager.findEntryPointByEmbedId",
158
+ "No matching entry point for embedId = $embedId"
159
+ )
160
+ }
161
+ return entryPoint
142
162
  }
143
163
 
144
164
  override fun onDropViewInstance(view: EmbedWebView) {
165
+ Logger.d("GamificationEmbedViewManager.onDropViewInstance", "Destroying embed view instance")
145
166
  preDrawListeners.remove(view)?.let { view.viewTreeObserver.removeOnPreDrawListener(it) }
146
167
  view.destroy()
147
168
  super.onDropViewInstance(view)
@@ -7,6 +7,7 @@ import com.facebook.react.module.annotations.ReactModule
7
7
  import com.facebook.react.uimanager.SimpleViewManager
8
8
  import com.facebook.react.uimanager.ThemedReactContext
9
9
  import com.facebook.react.uimanager.annotations.ReactProp
10
+ import com.capillary.hydra.android.sdk.utils.Logger
10
11
  import com.hydralib.hydrasdk.HydraCore
11
12
  import kotlinx.coroutines.CoroutineScope
12
13
  import kotlinx.coroutines.Dispatchers
@@ -22,11 +23,19 @@ class GamificationLoyaltyViewManager : SimpleViewManager<LoyaltyEmbedView>() {
22
23
 
23
24
  @ReactProp(name = "loyaltyId")
24
25
  fun setLoyaltyId(view: LoyaltyEmbedView, loyaltyId: String?) {
25
- if (loyaltyId.isNullOrEmpty()) return
26
+ if (loyaltyId.isNullOrEmpty()) {
27
+ Logger.e("GamificationLoyaltyViewManager.setLoyaltyId", "loyaltyId prop is null or empty, skipping bind")
28
+ return
29
+ }
30
+ // Guard: don't re-bind if already bound to this loyaltyId. Without this, every RN
31
+ // re-render calls bindWithLayout again, re-binding the WebView unnecessarily.
32
+ if (view.tag == loyaltyId) return
33
+ // findLoyaltyUrlById already logs the reason on failure (missing FlowManager or unmatched id)
26
34
  val loyaltyUrl = findLoyaltyUrlById(loyaltyId) ?: return
27
35
 
28
36
  // Defer bind until Yoga has measured and laid out the view
29
37
  if (view.width > 0 && view.height > 0) {
38
+ view.tag = loyaltyId
30
39
  bindWithLayout(view, loyaltyUrl)
31
40
  } else {
32
41
  view.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
@@ -36,6 +45,7 @@ class GamificationLoyaltyViewManager : SimpleViewManager<LoyaltyEmbedView>() {
36
45
  ) {
37
46
  if (right - left > 0 && bottom - top > 0) {
38
47
  view.removeOnLayoutChangeListener(this)
48
+ view.tag = loyaltyId
39
49
  bindWithLayout(view, loyaltyUrl)
40
50
  }
41
51
  }
@@ -51,8 +61,16 @@ class GamificationLoyaltyViewManager : SimpleViewManager<LoyaltyEmbedView>() {
51
61
  private fun bindWithLayout(view: LoyaltyEmbedView, loyaltyUrl: LoyaltyUrl) {
52
62
  val w = view.width
53
63
  val h = view.height
54
- val activity = HydraCore.curReactActivity ?: return
55
- val flowManager = HydraCore.getFlowManager() ?: return
64
+ val activity = HydraCore.curReactActivity
65
+ if (activity == null) {
66
+ Logger.e("GamificationLoyaltyViewManager.bindWithLayout", "curReactActivity is null, cannot bind loyalty view")
67
+ return
68
+ }
69
+ val flowManager = HydraCore.getFlowManager()
70
+ if (flowManager == null) {
71
+ Logger.e("GamificationLoyaltyViewManager.bindWithLayout", "FlowManager is null, cannot bind loyalty view")
72
+ return
73
+ }
56
74
 
57
75
  // Sync layoutParams with Yoga's actual pixel dimensions
58
76
  view.layoutParams?.let { lp ->
@@ -62,13 +80,18 @@ class GamificationLoyaltyViewManager : SimpleViewManager<LoyaltyEmbedView>() {
62
80
 
63
81
  // Resync after game played
64
82
  view.onGamePlayed = {
83
+ Logger.d("GamificationLoyaltyViewManager.onGamePlayed", "Game played, resyncing flow manager")
65
84
  CoroutineScope(Dispatchers.Main).launch {
66
85
  HydraCore.getFlowManager()?.resync()
67
86
  }
68
87
  }
69
88
 
70
89
  val capToken = flowManager.getCapToken()
71
- view.bind(loyaltyUrl.url, capToken, activity)
90
+ view.bind(loyaltyUrl.embedId, loyaltyUrl.url, capToken, activity)
91
+ Logger.d(
92
+ "GamificationLoyaltyViewManager.bindWithLayout",
93
+ "Bound loyalty view, url = ${loyaltyUrl.url}, size = ${w}x$h"
94
+ )
72
95
 
73
96
  // Force measure/layout of children — RN parent swallows requestLayout()
74
97
  // so LoyaltyEmbedView's internal WebView never gets measured otherwise
@@ -80,11 +103,23 @@ class GamificationLoyaltyViewManager : SimpleViewManager<LoyaltyEmbedView>() {
80
103
  }
81
104
 
82
105
  private fun findLoyaltyUrlById(loyaltyId: String): LoyaltyUrl? {
83
- val flowManager = HydraCore.getFlowManager() ?: return null
84
- return flowManager.getLoyaltyUrls().find { it.embedId == loyaltyId }
106
+ val flowManager = HydraCore.getFlowManager()
107
+ if (flowManager == null) {
108
+ Logger.e("GamificationLoyaltyViewManager.findLoyaltyUrlById", "FlowManager is null, cannot resolve loyaltyId = $loyaltyId")
109
+ return null
110
+ }
111
+ val loyaltyUrl = flowManager.getLoyaltyUrls().find { it.embedId == loyaltyId }
112
+ if (loyaltyUrl == null) {
113
+ Logger.e(
114
+ "GamificationLoyaltyViewManager.findLoyaltyUrlById",
115
+ "No matching loyalty URL for loyaltyId = $loyaltyId"
116
+ )
117
+ }
118
+ return loyaltyUrl
85
119
  }
86
120
 
87
121
  override fun onDropViewInstance(view: LoyaltyEmbedView) {
122
+ Logger.d("GamificationLoyaltyViewManager.onDropViewInstance", "Destroying loyalty view instance")
88
123
  view.destroy()
89
124
  super.onDropViewInstance(view)
90
125
  }
@@ -14,8 +14,10 @@ import com.capillary.hydra.android.sdk.NotificationCenterConfig
14
14
  import com.capillary.hydra.android.sdk.NotificationConfig
15
15
  import com.capillary.hydra.android.sdk.UserPreferences
16
16
  import com.capillary.hydra.android.sdk.utils.Logger
17
+ import com.capillary.hydra.gamification.GamificationCallback
17
18
  import com.capillary.hydra.gamification.GamificationFlowManager
18
19
  import com.capillary.hydra.gamification.GamificationRegion
20
+ import com.capillary.hydra.gamification.ThemeMode
19
21
  import com.capillary.hydra.gamification.ui.GamificationUIManager
20
22
  import com.facebook.react.ReactActivity
21
23
  import com.facebook.react.bridge.Arguments
@@ -241,12 +243,16 @@ object HydraCore {
241
243
  val cgWriteKey = hydraRemoteConfig.gamificationKey ?: ""
242
244
  val cgRegion = hydraRemoteConfig.gamificationRegion ?: ""
243
245
  val cgBaseUrl = hydraRemoteConfig.gamificationBaseUrl ?: ""
244
- if (cgWriteKey.isNotEmpty() && cgRegion.isNotEmpty()) {
246
+ if (cgWriteKey.isNotEmpty()) {
245
247
  gamificationWriteKey(cgWriteKey)
246
248
  try {
247
249
  gamificationRegion(GamificationRegion.valueOf(cgRegion.uppercase()))
248
- } catch (_: IllegalArgumentException) {
249
-
250
+ } catch (e: IllegalArgumentException) {
251
+ Log.e(
252
+ "HydraCore.initHydraInstance",
253
+ "Invalid gamification region \"$cgRegion\", does not match any GamificationRegion enum value",
254
+ e
255
+ )
250
256
  }
251
257
  }
252
258
  if (cgBaseUrl.isNotEmpty()) {
@@ -258,6 +264,22 @@ object HydraCore {
258
264
 
259
265
  hydraAPI?.getGamificationManager()?.let {
260
266
  flowManager = it
267
+ it.callback = object : GamificationCallback {
268
+ override fun onAnalyticsEvent(eventName: String, data: Map<String, Any?>) {
269
+ HydraEventEmitter.sendEvent(
270
+ HydraEventType.ON_GAMIFICATION_ANALYTICS_EVENT,
271
+ mapOf("name" to eventName, "payload" to data)
272
+ )
273
+ }
274
+
275
+ override fun onDeepLinkEvent(data: Map<String, Any?>) {
276
+ val deepLink = data["deepLink"] as? String ?: return
277
+ HydraEventEmitter.sendEvent(
278
+ HydraEventType.ON_GAMIFICATION_DEEPLINK,
279
+ deepLink
280
+ )
281
+ }
282
+ }
261
283
  // Synchronized against gamificationScreenAppeared's check-then-set so a screen name
262
284
  // set concurrently with init can't be missed by this read-and-clear.
263
285
  val pending = synchronized(gamificationLock) {
@@ -744,11 +766,13 @@ object HydraCore {
744
766
  if (hydraAPI == null) {
745
767
  Logger.e("HydraCore.reportUserEvent", "Hydra instance not initialized")
746
768
  promise?.reject("Error Reporting user event", "Hydra instance not initialized", Throwable())
769
+ return
747
770
  }
748
771
 
749
772
  if (eventName == null) {
750
773
  Logger.e("HydraCore.reportUserEvent", "Event name cannot be null")
751
774
  promise?.reject("Error Reporting user event", "Event name cannot be null", Throwable())
775
+ return
752
776
  }
753
777
 
754
778
  hydraAPI!!.pushEvent(eventName!!, attributes?.toMap())
@@ -781,7 +805,10 @@ object HydraCore {
781
805
  Logger.d("HydraCore.getCurrentUserState", "Current user state: $stateString")
782
806
  promise?.resolve(stateString)
783
807
  } catch (e: Exception) {
784
- Logger.e("HydraCore.getCurrentUserState", "Error getting current user state: ${e.message}")
808
+ Logger.e(
809
+ "HydraCore.getCurrentUserState",
810
+ "Error getting current user state: ${e.message}\n${e.stackTraceToString()}"
811
+ )
785
812
  promise?.reject("Error", "Failed to get current user state", e)
786
813
  }
787
814
  }
@@ -798,7 +825,10 @@ object HydraCore {
798
825
  promise?.resolve(null)
799
826
  }
800
827
  } catch (e: Exception) {
801
- Logger.e("HydraCore.getVisitorDetails", "Error getting visitor details: ${e.message}")
828
+ Logger.e(
829
+ "HydraCore.getVisitorDetails",
830
+ "Error getting visitor details: ${e.message}\n${e.stackTraceToString()}"
831
+ )
802
832
  promise?.reject("Error", "Failed to get visitor details", e)
803
833
  }
804
834
  }
@@ -813,7 +843,10 @@ object HydraCore {
813
843
  Logger.d("HydraCore.isVisitorFlowEnabled", "Visitor flow enabled: $isEnabled")
814
844
  promise?.resolve(isEnabled)
815
845
  } catch (e: Exception) {
816
- Logger.e("HydraCore.isVisitorFlowEnabled", "Error checking visitor flow: ${e.message}")
846
+ Logger.e(
847
+ "HydraCore.isVisitorFlowEnabled",
848
+ "Error checking visitor flow: ${e.message}\n${e.stackTraceToString()}"
849
+ )
817
850
  promise?.reject("Error", "Failed to check visitor flow", e)
818
851
  }
819
852
  }
@@ -857,4 +890,74 @@ object HydraCore {
857
890
  promise.resolve(null)
858
891
  }
859
892
 
893
+ fun setGamificationSuppressed(suppressed: Boolean, promise: Promise?) {
894
+ if (hydraAPI == null) {
895
+ Logger.e("HydraCore.setGamificationSuppressed", "Hydra instance not initialized")
896
+ promise?.reject(
897
+ "Error setting gamification suppressed",
898
+ "Hydra instance not initialized",
899
+ Throwable()
900
+ )
901
+ return
902
+ }
903
+
904
+ hydraAPI!!.setGamificationSuppressed(suppressed)
905
+ promise?.resolve(null)
906
+
907
+ Logger.d("HydraCore.setGamificationSuppressed", "Gamification suppressed set to $suppressed")
908
+ }
909
+
910
+ fun isGamificationSuppressed(promise: Promise?) {
911
+ if (hydraAPI == null) {
912
+ Logger.e("HydraCore.isGamificationSuppressed", "Hydra instance not initialized")
913
+ promise?.reject(
914
+ "Error reading gamification suppressed",
915
+ "Hydra instance not initialized",
916
+ Throwable()
917
+ )
918
+ return
919
+ }
920
+
921
+ promise?.resolve(hydraAPI!!.isGamificationSuppressed())
922
+ }
923
+
924
+ /** Case/whitespace-tolerant lookup, avoids relying on ThemeMode.valueOf()'s exact-match reflection. */
925
+ private fun parseThemeMode(mode: String): ThemeMode? =
926
+ when (mode.trim().uppercase()) {
927
+ "NONE" -> ThemeMode.NONE
928
+ "AUTO" -> ThemeMode.AUTO
929
+ "LIGHT" -> ThemeMode.LIGHT
930
+ "DARK" -> ThemeMode.DARK
931
+ else -> null
932
+ }
933
+
934
+ fun setThemeMode(mode: String?, promise: Promise?) {
935
+ if (mode == null) {
936
+ Logger.e("HydraCore.setThemeMode", "Theme mode is null")
937
+ promise?.reject("Error setting theme mode", "Theme mode is null", Throwable())
938
+ return
939
+ }
940
+
941
+ if (hydraAPI == null) {
942
+ Logger.e("HydraCore.setThemeMode", "HydraAPI is null")
943
+ promise?.reject("Error setting theme mode", "HydraAPI is null", Throwable())
944
+ return
945
+ }
946
+
947
+ val themeMode = parseThemeMode(mode)
948
+ if (themeMode == null) {
949
+ Logger.e("HydraCore.setThemeMode", "Invalid theme mode: $mode")
950
+ promise?.reject("Error setting theme mode", "Invalid theme mode: $mode", Throwable())
951
+ return
952
+ }
953
+
954
+ hydraAPI!!.setThemeMode(themeMode)
955
+ promise?.resolve(null)
956
+ }
957
+
958
+ fun getThemeMode(promise: Promise?) {
959
+ val mode = hydraAPI?.getThemeMode() ?: ThemeMode.NONE
960
+ promise?.resolve(mode.name)
961
+ }
962
+
860
963
  }