@billdog.io/react-native 1.0.0-beta.2

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 (309) hide show
  1. package/BillDogReactNative.podspec +30 -0
  2. package/LICENSE +21 -0
  3. package/README.md +330 -0
  4. package/android/build.gradle +99 -0
  5. package/android/src/main/AndroidManifest.xml +1 -0
  6. package/android/src/main/kotlin/io/billdog/reactnative/BillDogABTestModule.kt +317 -0
  7. package/android/src/main/kotlin/io/billdog/reactnative/BillDogAnalyticsModule.kt +389 -0
  8. package/android/src/main/kotlin/io/billdog/reactnative/BillDogModule.kt +817 -0
  9. package/android/src/main/kotlin/io/billdog/reactnative/BillDogPaywallViewManager.kt +383 -0
  10. package/android/src/main/kotlin/io/billdog/reactnative/BillDogSurveyModule.kt +427 -0
  11. package/android/src/main/kotlin/io/billdog/reactnative/BillDogVirtualCurrencyModule.kt +407 -0
  12. package/android/src/main/kotlin/io/billdog/virtualcurrency/BillDogVirtualCurrencyPackage.kt +44 -0
  13. package/dist/AdvancedPaywallCache.d.ts +70 -0
  14. package/dist/AdvancedPaywallCache.js +344 -0
  15. package/dist/BillDog.d.ts +255 -0
  16. package/dist/BillDog.js +918 -0
  17. package/dist/BillDogConfiguration.d.ts +145 -0
  18. package/dist/BillDogConfiguration.js +229 -0
  19. package/dist/ComponentRenderer.d.ts +17 -0
  20. package/dist/ComponentRenderer.js +886 -0
  21. package/dist/LayoutEngine.d.ts +101 -0
  22. package/dist/LayoutEngine.js +394 -0
  23. package/dist/LocalizationResolver.d.ts +99 -0
  24. package/dist/LocalizationResolver.js +266 -0
  25. package/dist/NativePaywallView.d.ts +36 -0
  26. package/dist/NativePaywallView.js +59 -0
  27. package/dist/PaywallCache.d.ts +74 -0
  28. package/dist/PaywallCache.js +293 -0
  29. package/dist/PaywallStateManager.d.ts +62 -0
  30. package/dist/PaywallStateManager.js +147 -0
  31. package/dist/PaywallView.d.ts +42 -0
  32. package/dist/PaywallView.js +275 -0
  33. package/dist/ProductVariableProvider.d.ts +60 -0
  34. package/dist/ProductVariableProvider.js +239 -0
  35. package/dist/VariableResolver.d.ts +120 -0
  36. package/dist/VariableResolver.js +379 -0
  37. package/dist/WebViewPaywall.d.ts +75 -0
  38. package/dist/WebViewPaywall.js +360 -0
  39. package/dist/animations/presets.d.ts +49 -0
  40. package/dist/animations/presets.js +367 -0
  41. package/dist/components/AccoladeCpsComponent.d.ts +13 -0
  42. package/dist/components/AccoladeCpsComponent.js +248 -0
  43. package/dist/components/AccordionComponent.d.ts +10 -0
  44. package/dist/components/AccordionComponent.js +187 -0
  45. package/dist/components/AnimatedWrapper.d.ts +35 -0
  46. package/dist/components/AnimatedWrapper.js +277 -0
  47. package/dist/components/CarouselComponent.d.ts +23 -0
  48. package/dist/components/CarouselComponent.js +484 -0
  49. package/dist/components/ChartCurveComponent.d.ts +13 -0
  50. package/dist/components/ChartCurveComponent.js +333 -0
  51. package/dist/components/CheckboxComponent.d.ts +9 -0
  52. package/dist/components/CheckboxComponent.js +172 -0
  53. package/dist/components/ConfettiComponent.d.ts +9 -0
  54. package/dist/components/ConfettiComponent.js +172 -0
  55. package/dist/components/CountdownComponent.d.ts +16 -0
  56. package/dist/components/CountdownComponent.js +414 -0
  57. package/dist/components/DelayedXComponent.d.ts +12 -0
  58. package/dist/components/DelayedXComponent.js +240 -0
  59. package/dist/components/DrawerModalComponent.d.ts +20 -0
  60. package/dist/components/DrawerModalComponent.js +286 -0
  61. package/dist/components/GradientView.d.ts +38 -0
  62. package/dist/components/GradientView.js +126 -0
  63. package/dist/components/HapticFeedbackComponent.d.ts +16 -0
  64. package/dist/components/HapticFeedbackComponent.js +64 -0
  65. package/dist/components/HorizontalProductsComponent.d.ts +17 -0
  66. package/dist/components/HorizontalProductsComponent.js +284 -0
  67. package/dist/components/MultiChoiceGridComponent.d.ts +13 -0
  68. package/dist/components/MultiChoiceGridComponent.js +234 -0
  69. package/dist/components/NPSScaleComponent.d.ts +16 -0
  70. package/dist/components/NPSScaleComponent.js +260 -0
  71. package/dist/components/NavigationComponent.d.ts +26 -0
  72. package/dist/components/NavigationComponent.js +302 -0
  73. package/dist/components/OAuthButtonComponent.d.ts +20 -0
  74. package/dist/components/OAuthButtonComponent.js +237 -0
  75. package/dist/components/ProgressLoaderComponent.d.ts +13 -0
  76. package/dist/components/ProgressLoaderComponent.js +308 -0
  77. package/dist/components/ProgressStepperComponent.d.ts +18 -0
  78. package/dist/components/ProgressStepperComponent.js +228 -0
  79. package/dist/components/SegmentedPickerComponent.d.ts +10 -0
  80. package/dist/components/SegmentedPickerComponent.js +221 -0
  81. package/dist/components/ShareButtonsComponent.d.ts +21 -0
  82. package/dist/components/ShareButtonsComponent.js +265 -0
  83. package/dist/components/ShareCodeComponent.d.ts +16 -0
  84. package/dist/components/ShareCodeComponent.js +274 -0
  85. package/dist/components/SingleChoiceGridComponent.d.ts +13 -0
  86. package/dist/components/SingleChoiceGridComponent.js +210 -0
  87. package/dist/components/SliderComponent.d.ts +9 -0
  88. package/dist/components/SliderComponent.js +263 -0
  89. package/dist/components/StarRatingInputComponent.d.ts +20 -0
  90. package/dist/components/StarRatingInputComponent.js +198 -0
  91. package/dist/components/StickerCompositeComponent.d.ts +13 -0
  92. package/dist/components/StickerCompositeComponent.js +136 -0
  93. package/dist/components/TabsComponent.d.ts +19 -0
  94. package/dist/components/TabsComponent.js +431 -0
  95. package/dist/components/TextInputComponent.d.ts +18 -0
  96. package/dist/components/TextInputComponent.js +227 -0
  97. package/dist/components/TimelineComponent.d.ts +19 -0
  98. package/dist/components/TimelineComponent.js +104 -0
  99. package/dist/components/ToggleComponent.d.ts +14 -0
  100. package/dist/components/ToggleComponent.js +132 -0
  101. package/dist/components/VerticalProductsComponent.d.ts +17 -0
  102. package/dist/components/VerticalProductsComponent.js +311 -0
  103. package/dist/dynamicValues.d.ts +36 -0
  104. package/dist/dynamicValues.js +142 -0
  105. package/dist/filters/dateFilters.d.ts +50 -0
  106. package/dist/filters/dateFilters.js +390 -0
  107. package/dist/filters/index.d.ts +18 -0
  108. package/dist/filters/index.js +253 -0
  109. package/dist/hooks/useOfferings.d.ts +76 -0
  110. package/dist/hooks/useOfferings.js +99 -0
  111. package/dist/hooks/usePaywall.d.ts +27 -0
  112. package/dist/hooks/usePaywall.js +114 -0
  113. package/dist/hooks/usePlacement.d.ts +34 -0
  114. package/dist/hooks/usePlacement.js +89 -0
  115. package/dist/hooks/useSubscription.d.ts +40 -0
  116. package/dist/hooks/useSubscription.js +97 -0
  117. package/dist/index.d.ts +98 -0
  118. package/dist/index.js +198 -0
  119. package/dist/models/BillDogError.d.ts +40 -0
  120. package/dist/models/BillDogError.js +11 -0
  121. package/dist/models/CacheFetchPolicy.d.ts +8 -0
  122. package/dist/models/CacheFetchPolicy.js +4 -0
  123. package/dist/models/CustomProduct.d.ts +32 -0
  124. package/dist/models/CustomProduct.js +15 -0
  125. package/dist/models/CustomerInfo.d.ts +17 -0
  126. package/dist/models/CustomerInfo.js +11 -0
  127. package/dist/models/Delegate.d.ts +20 -0
  128. package/dist/models/Delegate.js +11 -0
  129. package/dist/models/EntitlementInfo.d.ts +19 -0
  130. package/dist/models/EntitlementInfo.js +2 -0
  131. package/dist/models/Placement.d.ts +54 -0
  132. package/dist/models/Placement.js +15 -0
  133. package/dist/models/PurchaseParams.d.ts +39 -0
  134. package/dist/models/PurchaseParams.js +60 -0
  135. package/dist/models/PurchaseResult.d.ts +30 -0
  136. package/dist/models/PurchaseResult.js +15 -0
  137. package/dist/models/RestoreResult.d.ts +30 -0
  138. package/dist/models/RestoreResult.js +22 -0
  139. package/dist/models/RestoredItem.d.ts +26 -0
  140. package/dist/models/RestoredItem.js +13 -0
  141. package/dist/modules/ABTest.d.ts +65 -0
  142. package/dist/modules/ABTest.js +113 -0
  143. package/dist/modules/Analytics.d.ts +91 -0
  144. package/dist/modules/Analytics.js +182 -0
  145. package/dist/modules/Survey.d.ts +65 -0
  146. package/dist/modules/Survey.js +82 -0
  147. package/dist/modules/VirtualCurrency.d.ts +63 -0
  148. package/dist/modules/VirtualCurrency.js +147 -0
  149. package/dist/murmur.d.ts +23 -0
  150. package/dist/murmur.js +104 -0
  151. package/dist/notifications/LiveActivitiesManager.d.ts +47 -0
  152. package/dist/notifications/LiveActivitiesManager.js +106 -0
  153. package/dist/notifications/NotificationScheduler.d.ts +55 -0
  154. package/dist/notifications/NotificationScheduler.js +199 -0
  155. package/dist/notifications/PurchaseNotificationHandler.d.ts +24 -0
  156. package/dist/notifications/PurchaseNotificationHandler.js +55 -0
  157. package/dist/notifications/useDeviceToken.d.ts +36 -0
  158. package/dist/notifications/useDeviceToken.js +97 -0
  159. package/dist/notifications/useLiveActivity.d.ts +39 -0
  160. package/dist/notifications/useLiveActivity.js +104 -0
  161. package/dist/review/ReviewAnalytics.d.ts +34 -0
  162. package/dist/review/ReviewAnalytics.js +73 -0
  163. package/dist/review/ReviewManager.d.ts +83 -0
  164. package/dist/review/ReviewManager.js +339 -0
  165. package/dist/review/ReviewTimingManager.d.ts +82 -0
  166. package/dist/review/ReviewTimingManager.js +224 -0
  167. package/dist/survey-core/types.d.ts +215 -0
  168. package/dist/survey-core/types.js +8 -0
  169. package/dist/types.d.ts +438 -0
  170. package/dist/types.js +158 -0
  171. package/dist/utils/ApiClient.d.ts +65 -0
  172. package/dist/utils/ApiClient.js +132 -0
  173. package/dist/utils/CSSConverter.d.ts +49 -0
  174. package/dist/utils/CSSConverter.js +470 -0
  175. package/dist/utils/animations.d.ts +41 -0
  176. package/dist/utils/animations.js +194 -0
  177. package/dist/utils/calendarIntegration.d.ts +36 -0
  178. package/dist/utils/calendarIntegration.js +89 -0
  179. package/dist/utils/configurationResolver.d.ts +23 -0
  180. package/dist/utils/configurationResolver.js +78 -0
  181. package/dist/utils/deviceDetection.d.ts +38 -0
  182. package/dist/utils/deviceDetection.js +59 -0
  183. package/dist/utils/hapticFeedback.d.ts +17 -0
  184. package/dist/utils/hapticFeedback.js +53 -0
  185. package/dist/utils/iconMapper.d.ts +26 -0
  186. package/dist/utils/iconMapper.js +113 -0
  187. package/dist/utils/notificationPermissions.d.ts +13 -0
  188. package/dist/utils/notificationPermissions.js +51 -0
  189. package/dist/utils/priceFormatting.d.ts +19 -0
  190. package/dist/utils/priceFormatting.js +99 -0
  191. package/dist/utils/shareSheet.d.ts +17 -0
  192. package/dist/utils/shareSheet.js +42 -0
  193. package/dist/utils/subscriptionUpgrade.d.ts +84 -0
  194. package/dist/utils/subscriptionUpgrade.js +100 -0
  195. package/dist/valueUtils.d.ts +48 -0
  196. package/dist/valueUtils.js +129 -0
  197. package/dist/xFlowTags.d.ts +18 -0
  198. package/dist/xFlowTags.js +385 -0
  199. package/ios/BillDogABTest.m +50 -0
  200. package/ios/BillDogABTest.swift +188 -0
  201. package/ios/BillDogAnalytics.m +105 -0
  202. package/ios/BillDogAnalytics.swift +324 -0
  203. package/ios/BillDogBridge.m +222 -0
  204. package/ios/BillDogBridge.swift +715 -0
  205. package/ios/BillDogLiveActivities.m +41 -0
  206. package/ios/BillDogLiveActivities.swift +104 -0
  207. package/ios/BillDogPaywallViewManager.m +39 -0
  208. package/ios/BillDogPaywallViewManager.swift +361 -0
  209. package/ios/BillDogSurvey.m +34 -0
  210. package/ios/BillDogSurvey.swift +257 -0
  211. package/ios/BillDogVirtualCurrencyBridge.m +63 -0
  212. package/ios/BillDogVirtualCurrencyBridge.swift +511 -0
  213. package/junit.xml +25 -0
  214. package/package.json +61 -0
  215. package/react-native.config.js +13 -0
  216. package/src/AdvancedPaywallCache.ts +433 -0
  217. package/src/BillDog.ts +983 -0
  218. package/src/BillDogConfiguration.ts +318 -0
  219. package/src/ComponentRenderer.tsx +1325 -0
  220. package/src/LayoutEngine.ts +459 -0
  221. package/src/LocalizationResolver.ts +311 -0
  222. package/src/NativePaywallView.tsx +132 -0
  223. package/src/PaywallCache.ts +358 -0
  224. package/src/PaywallStateManager.ts +154 -0
  225. package/src/PaywallView.tsx +328 -0
  226. package/src/ProductVariableProvider.ts +282 -0
  227. package/src/VariableResolver.ts +516 -0
  228. package/src/WebViewPaywall.tsx +454 -0
  229. package/src/animations/presets.ts +421 -0
  230. package/src/components/AccoladeCpsComponent.tsx +259 -0
  231. package/src/components/AccordionComponent.tsx +213 -0
  232. package/src/components/AnimatedWrapper.tsx +316 -0
  233. package/src/components/CarouselComponent.tsx +615 -0
  234. package/src/components/ChartCurveComponent.tsx +452 -0
  235. package/src/components/CheckboxComponent.tsx +185 -0
  236. package/src/components/ConfettiComponent.tsx +178 -0
  237. package/src/components/CountdownComponent.tsx +454 -0
  238. package/src/components/DelayedXComponent.tsx +266 -0
  239. package/src/components/DrawerModalComponent.tsx +324 -0
  240. package/src/components/GradientView.tsx +129 -0
  241. package/src/components/HapticFeedbackComponent.tsx +39 -0
  242. package/src/components/HorizontalProductsComponent.tsx +329 -0
  243. package/src/components/MultiChoiceGridComponent.tsx +265 -0
  244. package/src/components/NPSScaleComponent.tsx +294 -0
  245. package/src/components/NavigationComponent.tsx +329 -0
  246. package/src/components/OAuthButtonComponent.tsx +266 -0
  247. package/src/components/ProgressLoaderComponent.tsx +366 -0
  248. package/src/components/ProgressStepperComponent.tsx +250 -0
  249. package/src/components/SegmentedPickerComponent.tsx +245 -0
  250. package/src/components/ShareButtonsComponent.tsx +299 -0
  251. package/src/components/ShareCodeComponent.tsx +303 -0
  252. package/src/components/SingleChoiceGridComponent.tsx +229 -0
  253. package/src/components/SliderComponent.tsx +280 -0
  254. package/src/components/StarRatingInputComponent.tsx +223 -0
  255. package/src/components/StickerCompositeComponent.tsx +168 -0
  256. package/src/components/TabsComponent.tsx +496 -0
  257. package/src/components/TextInputComponent.tsx +258 -0
  258. package/src/components/TimelineComponent.tsx +129 -0
  259. package/src/components/ToggleComponent.tsx +142 -0
  260. package/src/components/VerticalProductsComponent.tsx +357 -0
  261. package/src/dynamicValues.ts +180 -0
  262. package/src/filters/dateFilters.ts +411 -0
  263. package/src/filters/index.ts +281 -0
  264. package/src/hooks/useOfferings.ts +192 -0
  265. package/src/hooks/usePaywall.ts +154 -0
  266. package/src/hooks/usePlacement.ts +143 -0
  267. package/src/hooks/useSubscription.ts +154 -0
  268. package/src/index.ts +245 -0
  269. package/src/models/BillDogError.ts +24 -0
  270. package/src/models/CacheFetchPolicy.ts +13 -0
  271. package/src/models/CustomProduct.ts +33 -0
  272. package/src/models/CustomerInfo.ts +26 -0
  273. package/src/models/Delegate.ts +22 -0
  274. package/src/models/EntitlementInfo.ts +27 -0
  275. package/src/models/Placement.ts +50 -0
  276. package/src/models/PurchaseParams.ts +81 -0
  277. package/src/models/PurchaseResult.ts +32 -0
  278. package/src/models/RestoreResult.ts +27 -0
  279. package/src/models/RestoredItem.ts +25 -0
  280. package/src/modules/ABTest.ts +131 -0
  281. package/src/modules/Analytics.ts +227 -0
  282. package/src/modules/Survey.ts +126 -0
  283. package/src/modules/VirtualCurrency.ts +223 -0
  284. package/src/murmur.ts +102 -0
  285. package/src/notifications/LiveActivitiesManager.ts +140 -0
  286. package/src/notifications/NotificationScheduler.ts +292 -0
  287. package/src/notifications/PurchaseNotificationHandler.ts +67 -0
  288. package/src/notifications/useDeviceToken.ts +116 -0
  289. package/src/notifications/useLiveActivity.ts +125 -0
  290. package/src/review/ReviewAnalytics.ts +89 -0
  291. package/src/review/ReviewManager.ts +355 -0
  292. package/src/review/ReviewTimingManager.ts +265 -0
  293. package/src/survey-core/types.ts +244 -0
  294. package/src/types/ambient.d.ts +9 -0
  295. package/src/types.ts +680 -0
  296. package/src/utils/ApiClient.ts +163 -0
  297. package/src/utils/CSSConverter.ts +552 -0
  298. package/src/utils/animations.ts +232 -0
  299. package/src/utils/calendarIntegration.ts +113 -0
  300. package/src/utils/configurationResolver.ts +106 -0
  301. package/src/utils/deviceDetection.ts +83 -0
  302. package/src/utils/hapticFeedback.ts +55 -0
  303. package/src/utils/iconMapper.ts +102 -0
  304. package/src/utils/notificationPermissions.ts +48 -0
  305. package/src/utils/priceFormatting.ts +119 -0
  306. package/src/utils/shareSheet.ts +55 -0
  307. package/src/utils/subscriptionUpgrade.ts +183 -0
  308. package/src/valueUtils.ts +108 -0
  309. package/src/xFlowTags.ts +463 -0
@@ -0,0 +1,407 @@
1
+ package io.billdog.reactnative
2
+
3
+ import android.util.Log
4
+ import com.facebook.react.bridge.Arguments
5
+ import com.facebook.react.bridge.Promise
6
+ import com.facebook.react.bridge.ReactApplicationContext
7
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
8
+ import com.facebook.react.bridge.ReactMethod
9
+ import com.facebook.react.bridge.ReadableArray
10
+ import com.facebook.react.bridge.ReadableMap
11
+ import com.facebook.react.bridge.ReadableType
12
+ import com.facebook.react.bridge.WritableArray
13
+ import com.facebook.react.bridge.WritableMap
14
+ import com.facebook.react.modules.core.DeviceEventManagerModule
15
+ import kotlinx.coroutines.CoroutineScope
16
+ import kotlinx.coroutines.Dispatchers
17
+ import kotlinx.coroutines.SupervisorJob
18
+ import kotlinx.coroutines.cancel
19
+ import kotlinx.coroutines.launch
20
+ import kotlinx.coroutines.withContext
21
+ import org.json.JSONArray
22
+ import org.json.JSONObject
23
+ import java.io.BufferedReader
24
+ import java.io.InputStreamReader
25
+ import java.net.HttpURLConnection
26
+ import java.net.URL
27
+ import java.net.URLEncoder
28
+
29
+ /**
30
+ * BillDogVirtualCurrency — React Native native module for Android
31
+ * (`NativeModules.BillDogVirtualCurrency`).
32
+ *
33
+ * Standalone HTTP bridge that mirrors the iOS `BillDogVirtualCurrencyBridge.swift`
34
+ * 1:1 — same endpoints, same request bodies, same `X-BillDog-API-Key` header, and
35
+ * the same `onBalanceChanged` / `onTransaction` events. This is deliberately a
36
+ * self-contained bridge (not a delegation to `io.billdog:billdog-virtual-currency`)
37
+ * so the RN Virtual Currency surface behaves identically on iOS and Android and
38
+ * does not depend on the core module registry being wired with the VC satellite.
39
+ *
40
+ * The authoritative JS contract is `src/modules/VirtualCurrency.ts`; each
41
+ * `BillDogVirtualCurrency.<method>(...)` there maps to the identically-named
42
+ * `@ReactMethod` below.
43
+ *
44
+ * ⚠️ VERIFICATION: this file needs a Gradle/Android build
45
+ * (`./gradlew :billdog_react-native:compileDebugKotlin` or the example app build)
46
+ * to type-check — there is no Android toolchain in the authoring environment.
47
+ */
48
+ class BillDogVirtualCurrencyModule(private val reactContext: ReactApplicationContext) :
49
+ ReactContextBaseJavaModule(reactContext) {
50
+
51
+ private val TAG = "BillDogVirtualCurrency"
52
+
53
+ private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
54
+
55
+ private var baseUrl: String? = null
56
+ private var apiKey: String? = null
57
+ private var customerId: String? = null
58
+ private var enableLogging: Boolean = false
59
+
60
+ override fun getName(): String = "BillDogVirtualCurrency"
61
+
62
+ // ── Lifecycle ────────────────────────────────────────────────────────────
63
+
64
+ override fun invalidate() {
65
+ scope.cancel()
66
+ super.invalidate()
67
+ }
68
+
69
+ @Deprecated("Deprecated in RN; kept for older RN versions that call it instead of invalidate()")
70
+ override fun onCatalystInstanceDestroy() {
71
+ scope.cancel()
72
+ @Suppress("DEPRECATION")
73
+ super.onCatalystInstanceDestroy()
74
+ }
75
+
76
+ // NativeEventEmitter on the JS side requires these to exist on the module.
77
+ @ReactMethod
78
+ fun addListener(@Suppress("UNUSED_PARAMETER") eventName: String) { /* RCTEventEmitter — no-op. */ }
79
+
80
+ @ReactMethod
81
+ fun removeListeners(@Suppress("UNUSED_PARAMETER") count: Double) { /* RCTEventEmitter — no-op. */ }
82
+
83
+ // ── Initialization ───────────────────────────────────────────────────────
84
+
85
+ /**
86
+ * BillDogVirtualCurrency.initialize(config) — stores baseUrl / apiKey /
87
+ * customerId / enableLogging. Mirrors the iOS bridge's `initialize`.
88
+ */
89
+ @ReactMethod
90
+ fun initialize(config: ReadableMap, promise: Promise) {
91
+ val baseUrl = if (config.hasKey("baseUrl")) config.getString("baseUrl") else null
92
+ val apiKey = if (config.hasKey("apiKey")) config.getString("apiKey") else null
93
+ val customerId = if (config.hasKey("customerId")) config.getString("customerId") else null
94
+ if (baseUrl.isNullOrEmpty() || apiKey.isNullOrEmpty() || customerId.isNullOrEmpty()) {
95
+ promise.reject("INVALID_CONFIG", "Missing required configuration parameters")
96
+ return
97
+ }
98
+ this.baseUrl = baseUrl.trimEnd('/')
99
+ this.apiKey = apiKey
100
+ this.customerId = customerId
101
+ this.enableLogging = config.hasKey("enableLogging") && config.getBoolean("enableLogging")
102
+ if (enableLogging) Log.d(TAG, "Virtual Currency initialized for customer: $customerId")
103
+ promise.resolve(null)
104
+ }
105
+
106
+ // ── Balance operations ─────────────────────────────────────────────────────
107
+
108
+ @ReactMethod
109
+ fun getBalance(currencyId: String, promise: Promise) {
110
+ val customerId = this.customerId ?: return promise.reject(
111
+ "NOT_INITIALIZED", "Virtual Currency module not initialized"
112
+ )
113
+ run(promise) {
114
+ val q = "currency_id=${enc(currencyId)}&customer_id=${enc(customerId)}"
115
+ val json = httpGet("/virtual-currency/balances?$q")
116
+ json.getDouble("balance")
117
+ }
118
+ }
119
+
120
+ @ReactMethod
121
+ fun addCurrency(
122
+ currencyId: String,
123
+ amount: Double,
124
+ reason: String?,
125
+ metadata: ReadableMap?,
126
+ promise: Promise,
127
+ ) {
128
+ transaction(currencyId, amount, "earn", reason, metadata, "ADD_CURRENCY_ERROR", promise)
129
+ }
130
+
131
+ @ReactMethod
132
+ fun deductCurrency(
133
+ currencyId: String,
134
+ amount: Double,
135
+ reason: String?,
136
+ metadata: ReadableMap?,
137
+ promise: Promise,
138
+ ) {
139
+ // Deduct posts a negative amount, mirroring the iOS bridge.
140
+ transaction(currencyId, -amount, "spend", reason, metadata, "DEDUCT_CURRENCY_ERROR", promise)
141
+ }
142
+
143
+ @ReactMethod
144
+ fun grantCurrency(
145
+ currencyId: String,
146
+ amount: Double,
147
+ reason: String?,
148
+ promise: Promise,
149
+ ) {
150
+ transaction(currencyId, amount, "grant", reason, null, "GRANT_CURRENCY_ERROR", promise)
151
+ }
152
+
153
+ private fun transaction(
154
+ currencyId: String,
155
+ amount: Double,
156
+ type: String,
157
+ reason: String?,
158
+ metadata: ReadableMap?,
159
+ errorCode: String,
160
+ promise: Promise,
161
+ ) {
162
+ val customerId = this.customerId ?: return promise.reject(
163
+ "NOT_INITIALIZED", "Virtual Currency module not initialized"
164
+ )
165
+ scope.launch {
166
+ try {
167
+ val body = JSONObject().apply {
168
+ put("currency_id", currencyId)
169
+ put("customer_id", customerId)
170
+ put("amount", amount)
171
+ put("type", type)
172
+ if (reason != null) put("reason", reason)
173
+ if (metadata != null) put("metadata", readableMapToJson(metadata))
174
+ }
175
+ val json = withContext(Dispatchers.IO) { httpPost("/virtual-currency/transactions", body) }
176
+ val newBalance = json.getDouble("new_balance")
177
+ emit("onBalanceChanged", Arguments.createMap().apply {
178
+ putString("currencyId", currencyId)
179
+ putDouble("newBalance", newBalance)
180
+ })
181
+ emit("onTransaction", Arguments.createMap().apply {
182
+ putString("id", json.optString("id", ""))
183
+ putString("currencyId", currencyId)
184
+ putDouble("amount", amount)
185
+ putString("type", type)
186
+ if (reason != null) putString("reason", reason)
187
+ putDouble("newBalance", newBalance)
188
+ })
189
+ promise.resolve(newBalance)
190
+ } catch (e: Throwable) {
191
+ promise.reject(errorCode, e.message, e)
192
+ }
193
+ }
194
+ }
195
+
196
+ @ReactMethod
197
+ fun getTransactionHistory(currencyId: String?, limit: Double, promise: Promise) {
198
+ val customerId = this.customerId ?: return promise.reject(
199
+ "NOT_INITIALIZED", "Virtual Currency module not initialized"
200
+ )
201
+ run(promise) {
202
+ var path = "/virtual-currency/transactions/history?customer_id=${enc(customerId)}&limit=${limit.toInt()}"
203
+ if (!currencyId.isNullOrEmpty()) path += "&currency_id=${enc(currencyId)}"
204
+ val json = httpGet(path)
205
+ jsonArrayToWritable(json.optJSONArray("transactions") ?: JSONArray())
206
+ }
207
+ }
208
+
209
+ @ReactMethod
210
+ fun exchangeCurrency(
211
+ fromCurrencyId: String,
212
+ toCurrencyId: String,
213
+ fromAmount: Double,
214
+ promise: Promise,
215
+ ) {
216
+ val customerId = this.customerId ?: return promise.reject(
217
+ "NOT_INITIALIZED", "Virtual Currency module not initialized"
218
+ )
219
+ scope.launch {
220
+ try {
221
+ val body = JSONObject().apply {
222
+ put("from_currency_id", fromCurrencyId)
223
+ put("to_currency_id", toCurrencyId)
224
+ put("from_amount", fromAmount)
225
+ put("customer_id", customerId)
226
+ }
227
+ val json = withContext(Dispatchers.IO) { httpPost("/virtual-currency/exchange", body) }
228
+ // Build the camelCase ExchangeResult the JS `ExchangeResult` type expects.
229
+ val newFrom = json.optDouble("new_from_balance", 0.0)
230
+ val newTo = json.optDouble("new_to_balance", 0.0)
231
+ val result: WritableMap = Arguments.createMap().apply {
232
+ putBoolean("success", true)
233
+ putString("fromCurrencyId", fromCurrencyId)
234
+ putString("toCurrencyId", toCurrencyId)
235
+ putDouble("fromAmount", fromAmount)
236
+ putDouble("toAmount", json.optDouble("to_amount", 0.0))
237
+ putDouble("exchangeRate", json.optDouble("exchange_rate", 0.0))
238
+ putDouble("newFromBalance", newFrom)
239
+ putDouble("newToBalance", newTo)
240
+ }
241
+ emit("onBalanceChanged", Arguments.createMap().apply {
242
+ putString("currencyId", fromCurrencyId)
243
+ putDouble("newBalance", newFrom)
244
+ })
245
+ emit("onBalanceChanged", Arguments.createMap().apply {
246
+ putString("currencyId", toCurrencyId)
247
+ putDouble("newBalance", newTo)
248
+ })
249
+ promise.resolve(result)
250
+ } catch (e: Throwable) {
251
+ promise.reject("EXCHANGE_ERROR", e.message, e)
252
+ }
253
+ }
254
+ }
255
+
256
+ @ReactMethod
257
+ fun getAllCurrencies(promise: Promise) {
258
+ val customerId = this.customerId ?: return promise.reject(
259
+ "NOT_INITIALIZED", "Virtual Currency module not initialized"
260
+ )
261
+ run(promise) {
262
+ // All-balances route returns `data.balances = [{currency:{…}, balance}]`
263
+ // (NOT `currencies`); the JS layer normalizes the nested shape.
264
+ val json = httpGet("/virtual-currency/balances?customer_id=${enc(customerId)}")
265
+ jsonArrayToWritable(json.optJSONArray("balances") ?: JSONArray())
266
+ }
267
+ }
268
+
269
+ // ── Helpers ────────────────────────────────────────────────────────────────
270
+
271
+ /** Run [block] on the IO dispatcher; resolve/reject the promise. */
272
+ private fun run(promise: Promise, block: () -> Any?) {
273
+ scope.launch {
274
+ try {
275
+ val value = withContext(Dispatchers.IO) { block() }
276
+ promise.resolve(value)
277
+ } catch (e: Throwable) {
278
+ promise.reject("BILLDOG_VC_ERROR", e.message, e)
279
+ }
280
+ }
281
+ }
282
+
283
+ private fun emit(name: String, body: WritableMap) {
284
+ try {
285
+ reactContext
286
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
287
+ .emit(name, body)
288
+ } catch (_: Throwable) { /* no active JS context — drop the event */ }
289
+ }
290
+
291
+ private fun enc(s: String): String = URLEncoder.encode(s, "UTF-8")
292
+
293
+ private fun openConnection(path: String, method: String): HttpURLConnection {
294
+ val base = baseUrl ?: throw IllegalStateException("Configuration missing")
295
+ val key = apiKey ?: throw IllegalStateException("Configuration missing")
296
+ val conn = URL(base + path).openConnection() as HttpURLConnection
297
+ conn.requestMethod = method
298
+ conn.setRequestProperty("X-BillDog-API-Key", key)
299
+ conn.setRequestProperty("Content-Type", "application/json")
300
+ conn.setRequestProperty("Accept", "application/json")
301
+ conn.connectTimeout = 15000
302
+ conn.readTimeout = 15000
303
+ return conn
304
+ }
305
+
306
+ private fun httpGet(path: String): JSONObject {
307
+ val conn = openConnection(path, "GET")
308
+ return readJson(conn)
309
+ }
310
+
311
+ private fun httpPost(path: String, body: JSONObject): JSONObject {
312
+ val conn = openConnection(path, "POST")
313
+ conn.doOutput = true
314
+ conn.outputStream.use { it.write(body.toString().toByteArray(Charsets.UTF_8)) }
315
+ return readJson(conn)
316
+ }
317
+
318
+ private fun readJson(conn: HttpURLConnection): JSONObject {
319
+ try {
320
+ val code = conn.responseCode
321
+ val stream = if (code in 200..299) conn.inputStream else conn.errorStream
322
+ val text = stream?.let {
323
+ BufferedReader(InputStreamReader(it, Charsets.UTF_8)).use { r -> r.readText() }
324
+ } ?: ""
325
+ if (code !in 200..299) {
326
+ throw RuntimeException(if (text.isNotEmpty()) text else "HTTP $code")
327
+ }
328
+ val root = JSONObject(text)
329
+ // BillDog edge functions wrap payloads in { success, data, meta }
330
+ // (supabase/functions/_shared/response.ts). Descend into `data` so
331
+ // callers read `balance`/`transactions`/`new_balance` at the top.
332
+ return root.optJSONObject("data") ?: root
333
+ } finally {
334
+ conn.disconnect()
335
+ }
336
+ }
337
+
338
+ private fun readableMapToJson(map: ReadableMap): JSONObject {
339
+ val json = JSONObject()
340
+ val it = map.keySetIterator()
341
+ while (it.hasNextKey()) {
342
+ val key = it.nextKey()
343
+ when (map.getType(key)) {
344
+ ReadableType.Null -> json.put(key, JSONObject.NULL)
345
+ ReadableType.Boolean -> json.put(key, map.getBoolean(key))
346
+ ReadableType.Number -> json.put(key, map.getDouble(key))
347
+ ReadableType.String -> json.put(key, map.getString(key))
348
+ ReadableType.Map -> json.put(key, readableMapToJson(map.getMap(key)!!))
349
+ ReadableType.Array -> json.put(key, readableArrayToJson(map.getArray(key)!!))
350
+ }
351
+ }
352
+ return json
353
+ }
354
+
355
+ private fun readableArrayToJson(array: ReadableArray): JSONArray {
356
+ val json = JSONArray()
357
+ for (i in 0 until array.size()) {
358
+ when (array.getType(i)) {
359
+ ReadableType.Null -> json.put(JSONObject.NULL)
360
+ ReadableType.Boolean -> json.put(array.getBoolean(i))
361
+ ReadableType.Number -> json.put(array.getDouble(i))
362
+ ReadableType.String -> json.put(array.getString(i))
363
+ ReadableType.Map -> json.put(readableMapToJson(array.getMap(i)))
364
+ ReadableType.Array -> json.put(readableArrayToJson(array.getArray(i)))
365
+ }
366
+ }
367
+ return json
368
+ }
369
+
370
+ private fun jsonToWritable(json: JSONObject): WritableMap {
371
+ val map = Arguments.createMap()
372
+ val keys = json.keys()
373
+ while (keys.hasNext()) {
374
+ val key = keys.next()
375
+ when (val v = json.get(key)) {
376
+ JSONObject.NULL -> map.putNull(key)
377
+ is Boolean -> map.putBoolean(key, v)
378
+ is Int -> map.putInt(key, v)
379
+ is Long -> map.putDouble(key, v.toDouble())
380
+ is Double -> map.putDouble(key, v)
381
+ is String -> map.putString(key, v)
382
+ is JSONObject -> map.putMap(key, jsonToWritable(v))
383
+ is JSONArray -> map.putArray(key, jsonArrayToWritable(v))
384
+ else -> map.putString(key, v.toString())
385
+ }
386
+ }
387
+ return map
388
+ }
389
+
390
+ private fun jsonArrayToWritable(array: JSONArray): WritableArray {
391
+ val out = Arguments.createArray()
392
+ for (i in 0 until array.length()) {
393
+ when (val v = array.get(i)) {
394
+ JSONObject.NULL -> out.pushNull()
395
+ is Boolean -> out.pushBoolean(v)
396
+ is Int -> out.pushInt(v)
397
+ is Long -> out.pushDouble(v.toDouble())
398
+ is Double -> out.pushDouble(v)
399
+ is String -> out.pushString(v)
400
+ is JSONObject -> out.pushMap(jsonToWritable(v))
401
+ is JSONArray -> out.pushArray(jsonArrayToWritable(v))
402
+ else -> out.pushString(v.toString())
403
+ }
404
+ }
405
+ return out
406
+ }
407
+ }
@@ -0,0 +1,44 @@
1
+ package io.billdog.virtualcurrency
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+ import io.billdog.reactnative.BillDogModule
8
+ import io.billdog.reactnative.BillDogAnalyticsModule
9
+ import io.billdog.reactnative.BillDogABTestModule
10
+ import io.billdog.reactnative.BillDogSurveyModule
11
+ import io.billdog.reactnative.BillDogVirtualCurrencyModule
12
+ import io.billdog.reactnative.BillDogPaywallViewManager
13
+
14
+ /**
15
+ * ReactPackage for @billdog.io/react-native (Android).
16
+ *
17
+ * Thin-bridge architecture: the modules and view managers registered here delegate to
18
+ * the native BillDog Android SDK (io.billdog:billdog-core / billdog-compose) rather than
19
+ * reimplementing logic in JS.
20
+ *
21
+ * Phase 0 (this commit): module skeleton so autolinking resolves and the package compiles.
22
+ * Phase 2 registers the real native modules and the paywall view manager below.
23
+ */
24
+ class BillDogVirtualCurrencyPackage : ReactPackage {
25
+
26
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
27
+ // Thin-bridge native modules — each delegates to the native BillDog Android SDK,
28
+ // except BillDogVirtualCurrency which is a self-contained HTTP bridge mirroring
29
+ // the iOS BillDogVirtualCurrencyBridge.swift 1:1 (iOS/Android RN parity).
30
+ return listOf(
31
+ BillDogModule(reactContext), // NativeModules.BillDog (core, 41 methods)
32
+ BillDogAnalyticsModule(reactContext), // NativeModules.BillDogAnalytics
33
+ BillDogABTestModule(reactContext), // NativeModules.BillDogABTest
34
+ BillDogSurveyModule(reactContext), // NativeModules.BillDogSurvey
35
+ BillDogVirtualCurrencyModule(reactContext), // NativeModules.BillDogVirtualCurrency
36
+ )
37
+ }
38
+
39
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
40
+ // BDPaywallView — SimpleViewManager<FrameLayout> hosting a ComposeView +
41
+ // PaywallScreen(config) for inline (embedded) native paywalls.
42
+ return listOf(BillDogPaywallViewManager())
43
+ }
44
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Phase 4: Advanced Caching Features for React Native
3
+ * - Cache compression (reduce storage by 60%)
4
+ * - Multi-level caching (memory + disk)
5
+ * - Intelligent prefetching
6
+ * - Analytics tracking
7
+ */
8
+ export interface AdvancedCacheConfig {
9
+ enabled: boolean;
10
+ maxCacheAge: number;
11
+ compressionEnabled: boolean;
12
+ memoryCache: boolean;
13
+ maxMemoryEntries: number;
14
+ intelligentPrefetch: boolean;
15
+ analyticsEnabled: boolean;
16
+ }
17
+ export declare const DEFAULT_ADVANCED_CONFIG: AdvancedCacheConfig;
18
+ interface CacheAnalytics {
19
+ hits: number;
20
+ misses: number;
21
+ memoryHits: number;
22
+ diskHits: number;
23
+ compressionSavings: number;
24
+ averageRetrievalTime: number;
25
+ prefetchSuccessRate: number;
26
+ }
27
+ interface CachedPaywall {
28
+ paywall: any;
29
+ version: number;
30
+ cachedAt: number;
31
+ expiresAt: number;
32
+ etag?: string;
33
+ }
34
+ export declare class AdvancedPaywallCache {
35
+ private config;
36
+ private cacheDir;
37
+ private memoryCache;
38
+ private accessPatterns;
39
+ private analytics;
40
+ constructor(config?: Partial<AdvancedCacheConfig>);
41
+ private compressData;
42
+ private decompressData;
43
+ get(identifier: string): Promise<CachedPaywall | null>;
44
+ private getDiskCached;
45
+ set(identifier: string, paywall: CachedPaywall): Promise<void>;
46
+ private addToMemoryCache;
47
+ private findLeastRecentlyUsed;
48
+ private trackAccess;
49
+ private schedulePrefetch;
50
+ learnRelationship(fromId: string, toId: string): void;
51
+ private updateAverageRetrievalTime;
52
+ private saveAnalytics;
53
+ private loadAnalytics;
54
+ getAnalytics(): CacheAnalytics & {
55
+ hitRate: number;
56
+ memoryHitRate: number;
57
+ memorySize: number;
58
+ };
59
+ resetAnalytics(): void;
60
+ private saveAccessPatterns;
61
+ private loadAccessPatterns;
62
+ delete(identifier: string): Promise<void>;
63
+ clearAll(): Promise<void>;
64
+ getStats(): Promise<{
65
+ paywallCount: number;
66
+ memoryCount: number;
67
+ compressionSavings: number;
68
+ }>;
69
+ }
70
+ export {};