@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,116 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import PushNotification from 'react-native-push-notification';
3
+ import { Platform } from 'react-native';
4
+ import { ApiClient } from '../utils/ApiClient';
5
+
6
+ interface UseDeviceTokenOptions {
7
+ userId: string;
8
+ projectId: string;
9
+ /** @deprecated Use BillDogConfiguration.configure({ apiKey }) instead */
10
+ apiKey?: string;
11
+ onTokenRegistered?: (token: string) => void;
12
+ onError?: (error: Error) => void;
13
+ }
14
+
15
+ /**
16
+ * Hook to register device token for push notifications.
17
+ * Automatically registers the device token when the component mounts.
18
+ *
19
+ * Credentials are resolved from `BillDogConfiguration` by default.
20
+ *
21
+ * @example
22
+ * ```tsx
23
+ * import { useDeviceToken } from '@billdog.io/react-native';
24
+ *
25
+ * function App() {
26
+ * useDeviceToken({
27
+ * userId: 'user-123',
28
+ * projectId: 'project-456',
29
+ * onTokenRegistered: (token) => console.log('Token registered:', token),
30
+ * });
31
+ *
32
+ * return <YourApp />;
33
+ * }
34
+ * ```
35
+ */
36
+ export function useDeviceToken({
37
+ userId,
38
+ projectId,
39
+ apiKey: apiKeyOverride,
40
+ onTokenRegistered,
41
+ onError,
42
+ }: UseDeviceTokenOptions) {
43
+ const registeredRef = useRef(false);
44
+
45
+ useEffect(() => {
46
+ if (registeredRef.current) return;
47
+
48
+ const registerToken = async () => {
49
+ try {
50
+ PushNotification.configure({
51
+ onRegister: async (tokenData: any) => {
52
+ try {
53
+ const token = tokenData.token;
54
+ const platform = Platform.OS;
55
+
56
+ await ApiClient.post('register-device-token', {
57
+ userId,
58
+ projectId,
59
+ token,
60
+ platform,
61
+ }, {
62
+ apiKey: apiKeyOverride,
63
+ });
64
+
65
+ registeredRef.current = true;
66
+ onTokenRegistered?.(token);
67
+ } catch (error) {
68
+ console.error('Failed to register device token:', error);
69
+ onError?.(error as Error);
70
+ }
71
+ },
72
+ onNotification: (notification: any) => {
73
+ console.log('Notification received:', notification);
74
+ },
75
+ permissions: {
76
+ alert: true,
77
+ badge: true,
78
+ sound: true,
79
+ },
80
+ popInitialNotification: true,
81
+ requestPermissions: true,
82
+ });
83
+ } catch (error) {
84
+ console.error('Failed to register device token:', error);
85
+ onError?.(error as Error);
86
+ }
87
+ };
88
+
89
+ registerToken();
90
+
91
+ return () => {
92
+ PushNotification.unregister();
93
+ };
94
+ }, [userId, projectId, apiKeyOverride, onTokenRegistered, onError]);
95
+ }
96
+
97
+ /**
98
+ * Manually register a device token.
99
+ * Resolves the endpoint from BillDogConfiguration (api.billdog.io/v1).
100
+ */
101
+ export async function registerDeviceToken(
102
+ token: string,
103
+ userId: string,
104
+ projectId: string,
105
+ apiKey?: string,
106
+ platform?: 'ios' | 'android'
107
+ ): Promise<void> {
108
+ await ApiClient.post('register-device-token', {
109
+ userId,
110
+ projectId,
111
+ token,
112
+ platform: platform || 'ios',
113
+ }, {
114
+ apiKey,
115
+ });
116
+ }
@@ -0,0 +1,125 @@
1
+ import { useState, useEffect, useCallback } from 'react';
2
+ import { liveActivities, LiveActivityConfig, LiveActivityUpdate } from './LiveActivitiesManager';
3
+
4
+ export interface UseLiveActivityOptions {
5
+ autoStart?: boolean;
6
+ config?: LiveActivityConfig;
7
+ }
8
+
9
+ export interface UseLiveActivityReturn {
10
+ activityId: string | null;
11
+ isActive: boolean;
12
+ isSupported: boolean;
13
+ areEnabled: boolean;
14
+ startActivity: (config: LiveActivityConfig) => Promise<string>;
15
+ updateActivity: (update: LiveActivityUpdate) => Promise<void>;
16
+ endActivity: (finalState?: Record<string, any>) => Promise<void>;
17
+ error: Error | null;
18
+ }
19
+
20
+ /**
21
+ * React hook for managing a Live Activity
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * const { activityId, isActive, startActivity, updateActivity, endActivity } = useLiveActivity();
26
+ *
27
+ * // Start activity
28
+ * await startActivity({
29
+ * activityType: 'order_tracking',
30
+ * attributes: { orderNumber: '12345' },
31
+ * contentState: { status: 'preparing' }
32
+ * });
33
+ *
34
+ * // Update activity
35
+ * await updateActivity({
36
+ * contentState: { status: 'shipped' }
37
+ * });
38
+ *
39
+ * // End activity
40
+ * await endActivity({ status: 'delivered' });
41
+ * ```
42
+ */
43
+ export function useLiveActivity(options: UseLiveActivityOptions = {}): UseLiveActivityReturn {
44
+ const [activityId, setActivityId] = useState<string | null>(null);
45
+ const [isSupported, setIsSupported] = useState(false);
46
+ const [areEnabled, setAreEnabled] = useState(false);
47
+ const [error, setError] = useState<Error | null>(null);
48
+
49
+ useEffect(() => {
50
+ const checkSupport = async () => {
51
+ const supported = liveActivities.isSupported();
52
+ setIsSupported(supported);
53
+
54
+ if (supported) {
55
+ const enabled = await liveActivities.areActivitiesEnabled();
56
+ setAreEnabled(enabled);
57
+ }
58
+ };
59
+
60
+ checkSupport();
61
+ }, []);
62
+
63
+ useEffect(() => {
64
+ if (options.autoStart && options.config && isSupported && areEnabled && !activityId) {
65
+ startActivity(options.config).catch((err) => {
66
+ console.error('[useLiveActivity] Auto-start failed:', err);
67
+ });
68
+ }
69
+ }, [options.autoStart, options.config, isSupported, areEnabled, activityId]);
70
+
71
+ const startActivity = useCallback(async (config: LiveActivityConfig): Promise<string> => {
72
+ try {
73
+ setError(null);
74
+ const id = await liveActivities.startActivity(config);
75
+ setActivityId(id);
76
+ return id;
77
+ } catch (err) {
78
+ const error = err instanceof Error ? err : new Error('Failed to start activity');
79
+ setError(error);
80
+ throw error;
81
+ }
82
+ }, []);
83
+
84
+ const updateActivity = useCallback(async (update: LiveActivityUpdate): Promise<void> => {
85
+ if (!activityId) {
86
+ throw new Error('No active activity to update');
87
+ }
88
+
89
+ try {
90
+ setError(null);
91
+ await liveActivities.updateActivity(activityId, update);
92
+ } catch (err) {
93
+ const error = err instanceof Error ? err : new Error('Failed to update activity');
94
+ setError(error);
95
+ throw error;
96
+ }
97
+ }, [activityId]);
98
+
99
+ const endActivity = useCallback(async (finalState?: Record<string, any>): Promise<void> => {
100
+ if (!activityId) {
101
+ throw new Error('No active activity to end');
102
+ }
103
+
104
+ try {
105
+ setError(null);
106
+ await liveActivities.endActivity(activityId, finalState);
107
+ setActivityId(null);
108
+ } catch (err) {
109
+ const error = err instanceof Error ? err : new Error('Failed to end activity');
110
+ setError(error);
111
+ throw error;
112
+ }
113
+ }, [activityId]);
114
+
115
+ return {
116
+ activityId,
117
+ isActive: !!activityId,
118
+ isSupported,
119
+ areEnabled,
120
+ startActivity,
121
+ updateActivity,
122
+ endActivity,
123
+ error,
124
+ };
125
+ }
@@ -0,0 +1,89 @@
1
+ import { Platform } from 'react-native';
2
+ import { BillDogConfiguration } from '../BillDogConfiguration';
3
+ import { ApiClient } from '../utils/ApiClient';
4
+
5
+ export interface ReviewAnalyticsEvent {
6
+ eventType: 'requested' | 'shown' | 'dismissed' | 'completed' | 'error';
7
+ platform: 'ios' | 'android' | 'other';
8
+ customerId?: string;
9
+ sdkVersion?: string;
10
+ appVersion?: string;
11
+ osVersion?: string;
12
+ deviceModel?: string;
13
+ success?: boolean;
14
+ errorMessage?: string;
15
+ timeToShowMs?: number;
16
+ userContext?: Record<string, any>;
17
+ metadata?: Record<string, any>;
18
+ }
19
+
20
+ export class ReviewAnalytics {
21
+ /**
22
+ * @deprecated No longer needed. Credentials are read from BillDogConfiguration.
23
+ * This method is a no-op retained for backward compatibility.
24
+ */
25
+ static initialize(_apiKey: string, _projectUrl: string) {
26
+ console.warn(
27
+ '[ReviewAnalytics] initialize() is deprecated. ' +
28
+ 'Credentials are now read from BillDogConfiguration.configure(). This call is a no-op.'
29
+ );
30
+ }
31
+
32
+ /**
33
+ * Track a review event via the canonical `ingest-events` pipeline.
34
+ *
35
+ * Event naming follows `review_<eventType>` to preserve historical
36
+ * `bdanalytics_events.event_name` values written by the deprecated
37
+ * `track?type=review` router. Analytics surfaces (MessagingAnalytics,
38
+ * useReport, analytics-query) require no schema change.
39
+ *
40
+ * Per `mem://architecture/analytics-system-standard`: `ingest-events` is
41
+ * the sole analytics ingestion endpoint. Do not introduce alternative paths.
42
+ */
43
+ static async trackEvent(event: ReviewAnalyticsEvent): Promise<void> {
44
+ if (BillDogConfiguration.config.analyticsEnabled === false) {
45
+ console.log('[ReviewAnalytics] Analytics disabled, skipping event:', event.eventType);
46
+ return;
47
+ }
48
+
49
+ if (!BillDogConfiguration.isConfigured()) {
50
+ console.warn('[ReviewAnalytics] BillDogConfiguration not configured. Call BillDogConfiguration.configure() first.');
51
+ return;
52
+ }
53
+
54
+ const platform = event.platform || this.getPlatform();
55
+ const properties: Record<string, unknown> = {
56
+ platform,
57
+ sdk_version: event.sdkVersion,
58
+ app_version: event.appVersion,
59
+ os_version: event.osVersion || Platform.Version.toString(),
60
+ device_model: event.deviceModel,
61
+ success: event.success,
62
+ error_message: event.errorMessage,
63
+ time_to_show_ms: event.timeToShowMs,
64
+ user_context: event.userContext,
65
+ ...(event.metadata || {}),
66
+ };
67
+
68
+ try {
69
+ await ApiClient.post('ingest-events', {
70
+ events: [
71
+ {
72
+ event_name: `review_${event.eventType}`,
73
+ timestamp: new Date().toISOString(),
74
+ distinct_id: event.customerId,
75
+ properties,
76
+ },
77
+ ],
78
+ });
79
+ } catch (error) {
80
+ console.error('[ReviewAnalytics] Error tracking event:', error);
81
+ }
82
+ }
83
+
84
+ private static getPlatform(): 'ios' | 'android' | 'other' {
85
+ if (Platform.OS === 'ios') return 'ios';
86
+ if (Platform.OS === 'android') return 'android';
87
+ return 'other';
88
+ }
89
+ }
@@ -0,0 +1,355 @@
1
+ import { Platform, Linking } from 'react-native';
2
+ import { ReviewAnalytics } from './ReviewAnalytics';
3
+ import { ReviewTimingManager, ReviewTimingConfig } from './ReviewTimingManager';
4
+ import { BillDogConfiguration } from '../BillDogConfiguration';
5
+
6
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
7
+ const RN = (() => {
8
+ try {
9
+ return require('react-native');
10
+ } catch {
11
+ return null;
12
+ }
13
+ })();
14
+
15
+ const native: any = RN?.NativeModules?.BillDog ?? null;
16
+
17
+ let timingManager: ReviewTimingManager | null = null;
18
+
19
+ /**
20
+ * Optional native in-app-review module loader.
21
+ *
22
+ * `react-native` core does NOT export `StoreReview`/`InAppReview` — the previous
23
+ * implementation referenced non-existent exports, so the native prompt never ran
24
+ * and every request silently fell through to a (placeholder) store URL. The real
25
+ * native prompt requires `react-native-in-app-review` (cross-platform: iOS
26
+ * `SKStoreReviewController` + Android Play In-App Review), declared as an OPTIONAL
27
+ * peer dependency. Memoized; resolves `null` when the host app hasn't installed
28
+ * it (we then fall back to the configured store page).
29
+ */
30
+ let inAppReviewModule: unknown | undefined = undefined;
31
+ async function loadInAppReview(): Promise<any | null> {
32
+ if (inAppReviewModule !== undefined) return inAppReviewModule as any;
33
+ try {
34
+ // STATIC require: React Native's Metro bundler rejects dynamic
35
+ // `require(variable)` ("Invalid call … require(s)"), so the optional native
36
+ // module must be referenced by a literal specifier. The try/catch keeps it
37
+ // soft at runtime; the @ts-ignore avoids TS2307 at SDK build time when the
38
+ // optional peer is absent. Consumers who want native in-app review install
39
+ // `react-native-in-app-review` (declared in peerDependenciesMeta as optional).
40
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
41
+ // @ts-ignore optional peer dependency, may be absent at build time
42
+ const mod: any = require('react-native-in-app-review');
43
+ inAppReviewModule = (mod?.default ?? mod) ?? null;
44
+ } catch {
45
+ inAppReviewModule = null;
46
+ }
47
+ return inAppReviewModule as any;
48
+ }
49
+
50
+ /** True only when a store-page fallback is actually possible (IDs configured). */
51
+ function hasStoreFallback(): boolean {
52
+ const cfg = BillDogConfiguration.config as { appStoreId?: string; androidPackageName?: string };
53
+ return Platform.OS === 'ios' ? !!cfg.appStoreId : Platform.OS === 'android' ? !!cfg.androidPackageName : false;
54
+ }
55
+
56
+ /**
57
+ * Initialize review timing manager with configuration
58
+ * Call this on app launch to enable smart timing
59
+ *
60
+ * @param config - Timing configuration options
61
+ *
62
+ * @example
63
+ * ```typescript
64
+ * import { initializeReviewTiming } from '@billdog.io/react-native';
65
+ *
66
+ * await initializeReviewTiming({
67
+ * minDaysSinceInstall: 7,
68
+ * minSessionCount: 5,
69
+ * minSignificantEvents: 3,
70
+ * enableAutomaticTiming: true,
71
+ * });
72
+ * ```
73
+ */
74
+ export async function initializeReviewTiming(config?: ReviewTimingConfig): Promise<void> {
75
+ if (native && typeof native.initializeReviewTiming === 'function') {
76
+ try {
77
+ await native.initializeReviewTiming(config);
78
+ return;
79
+ } catch (error) {
80
+ console.error('Error invoking native initializeReviewTiming:', error);
81
+ }
82
+ }
83
+ timingManager = new ReviewTimingManager(config);
84
+ await timingManager.initialize();
85
+ await timingManager.recordSession();
86
+ }
87
+
88
+ /**
89
+ * Record a new app session (call on app launch)
90
+ */
91
+ export async function recordReviewSession(): Promise<void> {
92
+ if (native && typeof native.recordReviewSession === 'function') {
93
+ try {
94
+ await native.recordReviewSession();
95
+ return;
96
+ } catch (e) {}
97
+ }
98
+ if (timingManager) {
99
+ await timingManager.recordSession();
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Record a significant event (e.g., purchase, completed action)
105
+ * These events help determine optimal timing for review requests
106
+ */
107
+ export async function recordSignificantEvent(): Promise<void> {
108
+ if (native && typeof native.recordSignificantEvent === 'function') {
109
+ try {
110
+ await native.recordSignificantEvent();
111
+ return;
112
+ } catch (e) {}
113
+ }
114
+ if (timingManager) {
115
+ await timingManager.recordSignificantEvent();
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Check if it's a good time to request a review based on timing logic
121
+ *
122
+ * @returns Object with shouldRequest flag, reason, and timing data
123
+ */
124
+ export async function shouldRequestReview(): Promise<{
125
+ shouldRequest: boolean;
126
+ reason?: string;
127
+ timingData?: any;
128
+ }> {
129
+ if (native && typeof native.shouldRequestReview === 'function') {
130
+ try {
131
+ const should = await native.shouldRequestReview();
132
+ return { shouldRequest: should, reason: should ? 'All timing conditions met' : 'Timing conditions not met' };
133
+ } catch (e) {}
134
+ }
135
+ if (!timingManager) {
136
+ return { shouldRequest: true, reason: 'Timing manager not initialized' };
137
+ }
138
+ return await timingManager.shouldRequestReview();
139
+ }
140
+
141
+ /**
142
+ * Request in-app review from the user
143
+ * Optionally checks timing logic before showing
144
+ *
145
+ * @param options - Options for review request
146
+ * @param options.checkTiming - Whether to check timing logic before showing (default: true)
147
+ * @returns Promise with success status and whether review was shown
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * import { requestReview } from '@billdog.io/react-native';
152
+ *
153
+ * const { success, shown } = await requestReview();
154
+ * if (shown) {
155
+ * console.log('Review prompt shown to user');
156
+ * }
157
+ * ```
158
+ */
159
+ export async function requestReview(options: { checkTiming?: boolean } = {}): Promise<{
160
+ success: boolean;
161
+ shown: boolean;
162
+ reason?: string;
163
+ }> {
164
+ const { checkTiming = true } = options;
165
+ const startTime = Date.now();
166
+
167
+ try {
168
+ if (native && typeof native.requestReview === 'function') {
169
+ if (checkTiming) {
170
+ const timingCheck = await shouldRequestReview();
171
+ if (!timingCheck.shouldRequest) {
172
+ console.log('Review request skipped:', timingCheck.reason);
173
+ return {
174
+ success: false,
175
+ shown: false,
176
+ reason: timingCheck.reason
177
+ };
178
+ }
179
+ }
180
+
181
+ const result = await native.requestReview();
182
+ await ReviewAnalytics.trackEvent({
183
+ eventType: 'shown',
184
+ platform: Platform.OS === 'ios' ? 'ios' : Platform.OS === 'android' ? 'android' : 'other',
185
+ success: !!result?.success,
186
+ timeToShowMs: Date.now() - startTime,
187
+ });
188
+ return {
189
+ success: !!result?.success,
190
+ shown: !!result?.shown,
191
+ reason: result?.reason
192
+ };
193
+ }
194
+
195
+ // Check timing logic if enabled
196
+ if (checkTiming && timingManager) {
197
+ const timingCheck = await timingManager.shouldRequestReview();
198
+ if (!timingCheck.shouldRequest) {
199
+ console.log('Review request skipped:', timingCheck.reason);
200
+ return {
201
+ success: false,
202
+ shown: false,
203
+ reason: timingCheck.reason
204
+ };
205
+ }
206
+ }
207
+
208
+ // Record that we're requesting a review
209
+ if (timingManager) {
210
+ await timingManager.recordReviewRequested();
211
+ }
212
+ // Track request event
213
+ await ReviewAnalytics.trackEvent({
214
+ eventType: 'requested',
215
+ platform: Platform.OS === 'ios' ? 'ios' : Platform.OS === 'android' ? 'android' : 'other',
216
+ });
217
+
218
+ // Native in-app review — iOS SKStoreReviewController / Android Play In-App
219
+ // Review — via the optional `react-native-in-app-review` module (one call
220
+ // handles both platforms). Audit fix: the old code imported StoreReview/
221
+ // InAppReview from `react-native` (no such exports) so this branch never ran.
222
+ const InAppReview = await loadInAppReview();
223
+ if (
224
+ InAppReview &&
225
+ typeof InAppReview.isAvailable === 'function' &&
226
+ InAppReview.isAvailable() &&
227
+ typeof InAppReview.RequestInAppReview === 'function'
228
+ ) {
229
+ // Resolves true when the flow launched/finished. Note: neither platform
230
+ // reports whether the dialog displayed or whether the user submitted a
231
+ // review, so `shown` is best-effort. We do NOT auto-record "completed"
232
+ // (that permanently blocked future prompts and made maxRequestsPerUser
233
+ // unreachable); recordReviewRequested() above is the correct cap.
234
+ const flowFinished = await InAppReview.RequestInAppReview();
235
+ await ReviewAnalytics.trackEvent({
236
+ eventType: 'shown',
237
+ platform: Platform.OS === 'ios' ? 'ios' : Platform.OS === 'android' ? 'android' : 'other',
238
+ success: !!flowFinished,
239
+ timeToShowMs: Date.now() - startTime,
240
+ });
241
+ return { success: !!flowFinished, shown: !!flowFinished };
242
+ }
243
+
244
+ // Fallback: open store page (only when store IDs are configured)
245
+ const result = await openStoreForReview();
246
+
247
+ if (result.success) {
248
+ await ReviewAnalytics.trackEvent({
249
+ eventType: 'shown',
250
+ platform: Platform.OS === 'ios' ? 'ios' : Platform.OS === 'android' ? 'android' : 'other',
251
+ success: true,
252
+ timeToShowMs: Date.now() - startTime,
253
+ });
254
+ // Audit fix — removed the auto recordReviewCompleted() call (see above): it
255
+ // permanently blocked future prompts and made maxRequestsPerUser unreachable.
256
+ // recordReviewRequested() already counted this attempt toward the cap.
257
+ }
258
+
259
+ return result;
260
+ } catch (error) {
261
+ console.error('Error requesting review:', error);
262
+
263
+ // Track error event
264
+ await ReviewAnalytics.trackEvent({
265
+ eventType: 'error',
266
+ platform: Platform.OS === 'ios' ? 'ios' : Platform.OS === 'android' ? 'android' : 'other',
267
+ success: false,
268
+ errorMessage: error instanceof Error ? error.message : 'Unknown error',
269
+ });
270
+
271
+ return { success: false, shown: false };
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Check if review request is available
277
+ * Respects platform rate limiting
278
+ *
279
+ * @returns Promise with availability status
280
+ *
281
+ * @example
282
+ * ```typescript
283
+ * import { canRequestReview } from '@billdog.io/react-native';
284
+ *
285
+ * const { available } = await canRequestReview();
286
+ * if (available) {
287
+ * await requestReview();
288
+ * }
289
+ * ```
290
+ */
291
+ export async function canRequestReview(): Promise<{ available: boolean }> {
292
+ if (native && typeof native.canRequestReview === 'function') {
293
+ try {
294
+ const available = await native.canRequestReview();
295
+ return { available: !!available };
296
+ } catch (e) {}
297
+ }
298
+ try {
299
+ const InAppReview = await loadInAppReview();
300
+ if (InAppReview && typeof InAppReview.isAvailable === 'function') {
301
+ return { available: !!InAppReview.isAvailable() };
302
+ }
303
+ // No native module installed — only the store-page fallback is possible,
304
+ // and only if store IDs are configured. (Previously this returned `true`
305
+ // unconditionally even though the fallback URL was a placeholder.)
306
+ return { available: hasStoreFallback() };
307
+ } catch (error) {
308
+ console.error('Error checking review availability:', error);
309
+ return { available: false };
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Open the app store for manual review
315
+ * Used as fallback when native review is unavailable
316
+ */
317
+ async function openStoreForReview(): Promise<{ success: boolean; shown: boolean }> {
318
+ try {
319
+ // Audit fix — source real store IDs from BillDogConfiguration instead of the
320
+ // old `YOUR_APP_ID` / `YOUR_PACKAGE_NAME` placeholders (which produced a
321
+ // broken deep-link). If neither a native module nor a configured ID exists,
322
+ // report unavailable rather than opening a dead URL.
323
+ const cfg = BillDogConfiguration.config as { appStoreId?: string; androidPackageName?: string };
324
+ let storeUrl: string | null = null;
325
+
326
+ if (Platform.OS === 'ios') {
327
+ if (cfg.appStoreId) {
328
+ storeUrl = `itms-apps://itunes.apple.com/app/id${cfg.appStoreId}?action=write-review`;
329
+ }
330
+ } else if (Platform.OS === 'android') {
331
+ if (cfg.androidPackageName) {
332
+ storeUrl = `market://details?id=${cfg.androidPackageName}`;
333
+ }
334
+ }
335
+
336
+ if (!storeUrl) {
337
+ console.warn(
338
+ '[BillDog] Cannot request a review: install `react-native-in-app-review` for the native prompt, ' +
339
+ 'or set `appStoreId` (iOS) / `androidPackageName` (Android) in BillDogConfiguration.configure() for the store-page fallback.'
340
+ );
341
+ return { success: false, shown: false };
342
+ }
343
+
344
+ const canOpen = await Linking.canOpenURL(storeUrl);
345
+ if (canOpen) {
346
+ await Linking.openURL(storeUrl);
347
+ return { success: true, shown: true };
348
+ }
349
+
350
+ return { success: false, shown: false };
351
+ } catch (error) {
352
+ console.error('Error opening store:', error);
353
+ return { success: false, shown: false };
354
+ }
355
+ }