@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
package/src/murmur.ts ADDED
@@ -0,0 +1,102 @@
1
+ /**
2
+ * MurmurHash3 (x86_32) — the ONE canonical deterministic bucketing hash.
3
+ *
4
+ * React Native port of the backend canonical `murmurhash3`
5
+ * (`supabase/functions/_shared/edge-serving/pure.ts`) and the dashboard copy
6
+ * (`src/lib/murmur.ts`). Used to derive the render-time `user.seed` Dynamic
7
+ * Values variable so a user buckets IDENTICALLY on every surface.
8
+ *
9
+ * RN-SPECIFIC: uses a manual UTF-8 encoder instead of `TextEncoder`, because
10
+ * Hermes historically ships without `TextEncoder`. The bytes it produces are
11
+ * identical to `TextEncoder().encode(...)` for the fixture inputs (incl.
12
+ * surrogate-pair emoji), so it stays pinned to the same corpus.
13
+ *
14
+ * CONTRACT (do not change without regenerating the fixture on ALL ports):
15
+ * user.seed = murmurhash3(appUserId) % 1000 → range 0..999
16
+ *
17
+ * Conformance-pinned to `native-sdks/_test-fixtures/conformance/core_user_seed.json`.
18
+ * Never validated against another SDK port.
19
+ */
20
+
21
+ /** UTF-8 encode without depending on TextEncoder (Hermes-safe). */
22
+ function utf8Bytes(str: string): number[] {
23
+ const bytes: number[] = [];
24
+ for (let i = 0; i < str.length; i++) {
25
+ let code = str.charCodeAt(i);
26
+ if (code < 0x80) {
27
+ bytes.push(code);
28
+ } else if (code < 0x800) {
29
+ bytes.push(0xc0 | (code >> 6), 0x80 | (code & 0x3f));
30
+ } else if (code >= 0xd800 && code <= 0xdbff) {
31
+ // High surrogate — must be followed by a low surrogate.
32
+ const lo = str.charCodeAt(i + 1);
33
+ if (lo >= 0xdc00 && lo <= 0xdfff) {
34
+ i++;
35
+ code = 0x10000 + ((code - 0xd800) << 10) + (lo - 0xdc00);
36
+ bytes.push(
37
+ 0xf0 | (code >> 18),
38
+ 0x80 | ((code >> 12) & 0x3f),
39
+ 0x80 | ((code >> 6) & 0x3f),
40
+ 0x80 | (code & 0x3f),
41
+ );
42
+ } else {
43
+ // Lone high surrogate → U+FFFD replacement char, matching TextEncoder.
44
+ bytes.push(0xef, 0xbf, 0xbd);
45
+ }
46
+ } else if (code >= 0xdc00 && code <= 0xdfff) {
47
+ // Lone low surrogate → U+FFFD, matching TextEncoder.
48
+ bytes.push(0xef, 0xbf, 0xbd);
49
+ } else {
50
+ bytes.push(0xe0 | (code >> 12), 0x80 | ((code >> 6) & 0x3f), 0x80 | (code & 0x3f));
51
+ }
52
+ }
53
+ return bytes;
54
+ }
55
+
56
+ export function murmurhash3(key: string, seed = 0): number {
57
+ const keyBytes = utf8Bytes(key);
58
+ let h1 = seed;
59
+ const c1 = 0xcc9e2d51;
60
+ const c2 = 0x1b873593;
61
+ const blocks = Math.floor(keyBytes.length / 4);
62
+ for (let i = 0; i < blocks; i++) {
63
+ let k1 = keyBytes[i * 4] | (keyBytes[i * 4 + 1] << 8) | (keyBytes[i * 4 + 2] << 16) | (keyBytes[i * 4 + 3] << 24);
64
+ k1 = Math.imul(k1, c1);
65
+ k1 = (k1 << 15) | (k1 >>> 17);
66
+ k1 = Math.imul(k1, c2);
67
+ h1 ^= k1;
68
+ h1 = (h1 << 13) | (h1 >>> 19);
69
+ h1 = Math.imul(h1, 5) + 0xe6546b64;
70
+ }
71
+ let k1 = 0;
72
+ const offset = blocks * 4;
73
+ switch (keyBytes.length % 4) {
74
+ case 3:
75
+ k1 ^= keyBytes[offset + 2] << 16;
76
+ /* falls through */
77
+ case 2:
78
+ k1 ^= keyBytes[offset + 1] << 8;
79
+ /* falls through */
80
+ case 1:
81
+ k1 ^= keyBytes[offset];
82
+ k1 = Math.imul(k1, c1);
83
+ k1 = (k1 << 15) | (k1 >>> 17);
84
+ k1 = Math.imul(k1, c2);
85
+ h1 ^= k1;
86
+ }
87
+ h1 ^= keyBytes.length;
88
+ h1 ^= h1 >>> 16;
89
+ h1 = Math.imul(h1, 0x85ebca6b);
90
+ h1 ^= h1 >>> 13;
91
+ h1 = Math.imul(h1, 0xc2b2ae35);
92
+ h1 ^= h1 >>> 16;
93
+ return h1 >>> 0;
94
+ }
95
+
96
+ export const USER_SEED_MODULUS = 1000;
97
+
98
+ /** Canonical `user.seed` derivation: 0..999 bucket from a stable user id. */
99
+ export function userSeedBucket(appUserId: string | null | undefined): number {
100
+ if (!appUserId) return 0;
101
+ return murmurhash3(appUserId) % USER_SEED_MODULUS;
102
+ }
@@ -0,0 +1,140 @@
1
+ import { NativeModules, Platform } from 'react-native';
2
+
3
+ const { BillDogLiveActivities } = NativeModules;
4
+
5
+ export interface LiveActivityConfig {
6
+ activityType: string;
7
+ attributes: Record<string, any>;
8
+ contentState: Record<string, any>;
9
+ staleDate?: Date;
10
+ dismissalDate?: Date;
11
+ }
12
+
13
+ export interface LiveActivityUpdate {
14
+ contentState: Record<string, any>;
15
+ staleDate?: Date;
16
+ }
17
+
18
+ /**
19
+ * Manager for iOS Live Activities
20
+ * Only available on iOS 16.1+
21
+ */
22
+ export class LiveActivitiesManager {
23
+ private static instance: LiveActivitiesManager;
24
+
25
+ private constructor() {}
26
+
27
+ static getInstance(): LiveActivitiesManager {
28
+ if (!LiveActivitiesManager.instance) {
29
+ LiveActivitiesManager.instance = new LiveActivitiesManager();
30
+ }
31
+ return LiveActivitiesManager.instance;
32
+ }
33
+
34
+ /**
35
+ * Check if Live Activities are supported
36
+ */
37
+ isSupported(): boolean {
38
+ return Platform.OS === 'ios' && Platform.Version >= '16.2' && !!BillDogLiveActivities;
39
+ }
40
+
41
+ /**
42
+ * Check if Live Activities are enabled by the user
43
+ */
44
+ async areActivitiesEnabled(): Promise<boolean> {
45
+ if (!this.isSupported()) {
46
+ return false;
47
+ }
48
+
49
+ try {
50
+ return await BillDogLiveActivities.areActivitiesEnabled();
51
+ } catch (error) {
52
+ console.error('[LiveActivitiesManager] Error checking enabled status:', error);
53
+ return false;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Start a new Live Activity
59
+ * Returns the activity ID
60
+ */
61
+ async startActivity(config: LiveActivityConfig): Promise<string> {
62
+ if (!this.isSupported()) {
63
+ throw new Error('Live Activities are not supported on this platform');
64
+ }
65
+
66
+ try {
67
+ const activityId = await BillDogLiveActivities.startActivity(
68
+ config.activityType,
69
+ config.attributes,
70
+ config.contentState,
71
+ config.staleDate?.toISOString(),
72
+ config.dismissalDate?.toISOString()
73
+ );
74
+
75
+ return activityId;
76
+ } catch (error) {
77
+ console.error('[LiveActivitiesManager] Error starting activity:', error);
78
+ throw error;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Update an existing Live Activity
84
+ */
85
+ async updateActivity(activityId: string, update: LiveActivityUpdate): Promise<void> {
86
+ if (!this.isSupported()) {
87
+ throw new Error('Live Activities are not supported on this platform');
88
+ }
89
+
90
+ try {
91
+ await BillDogLiveActivities.updateActivity(
92
+ activityId,
93
+ update.contentState,
94
+ update.staleDate?.toISOString()
95
+ );
96
+ } catch (error) {
97
+ console.error('[LiveActivitiesManager] Error updating activity:', error);
98
+ throw error;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * End a Live Activity
104
+ */
105
+ async endActivity(
106
+ activityId: string,
107
+ finalState?: Record<string, any>,
108
+ dismissalPolicy: 'default' | 'immediate' | 'after' = 'default'
109
+ ): Promise<void> {
110
+ if (!this.isSupported()) {
111
+ throw new Error('Live Activities are not supported on this platform');
112
+ }
113
+
114
+ try {
115
+ await BillDogLiveActivities.endActivity(activityId, finalState, dismissalPolicy);
116
+ } catch (error) {
117
+ console.error('[LiveActivitiesManager] Error ending activity:', error);
118
+ throw error;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Get all active Live Activities
124
+ * Returns array of activity IDs
125
+ */
126
+ async getActiveActivities(): Promise<string[]> {
127
+ if (!this.isSupported()) {
128
+ return [];
129
+ }
130
+
131
+ try {
132
+ return await BillDogLiveActivities.getActiveActivities();
133
+ } catch (error) {
134
+ console.error('[LiveActivitiesManager] Error getting active activities:', error);
135
+ return [];
136
+ }
137
+ }
138
+ }
139
+
140
+ export const liveActivities = LiveActivitiesManager.getInstance();
@@ -0,0 +1,292 @@
1
+ import { Platform, PermissionsAndroid } from 'react-native';
2
+ import PushNotification from 'react-native-push-notification';
3
+ import PushNotificationIOS from '@react-native-community/push-notification-ios';
4
+ import { ApiClient } from '../utils/ApiClient';
5
+
6
+ /**
7
+ * Handles scheduling and managing trial reminder notifications for React Native.
8
+ *
9
+ * All API calls route through `ApiClient` → `BillDogConfiguration.apiBaseUrl`
10
+ * (the single branded host, `https://api.billdog.io/v1`).
11
+ */
12
+ export class NotificationScheduler {
13
+ private static instance: NotificationScheduler;
14
+
15
+ private constructor() {
16
+ this.configure();
17
+ }
18
+
19
+ public static getInstance(): NotificationScheduler {
20
+ if (!NotificationScheduler.instance) {
21
+ NotificationScheduler.instance = new NotificationScheduler();
22
+ }
23
+ return NotificationScheduler.instance;
24
+ }
25
+
26
+ private configure() {
27
+ PushNotification.configure({
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- callback shape comes from the optional, untyped react-native-push-notification peer dep
29
+ onNotification: (notification: any) => {
30
+ console.log('Notification received:', notification);
31
+ if (Platform.OS === 'ios') {
32
+ notification.finish(PushNotificationIOS.FetchResult.NoData);
33
+ }
34
+ },
35
+ permissions: {
36
+ alert: true,
37
+ badge: true,
38
+ sound: true,
39
+ },
40
+ popInitialNotification: true,
41
+ requestPermissions: false,
42
+ });
43
+
44
+ if (Platform.OS === 'android') {
45
+ PushNotification.createChannel(
46
+ {
47
+ channelId: 'trial-reminders',
48
+ channelName: 'Trial Reminders',
49
+ channelDescription: 'Notifications about your trial subscription',
50
+ importance: 4,
51
+ vibrate: true,
52
+ },
53
+ (created: boolean) => console.log(`Channel created: ${created}`)
54
+ );
55
+ }
56
+ }
57
+
58
+ public async requestPermissions(): Promise<boolean> {
59
+ if (Platform.OS === 'ios') {
60
+ return new Promise((resolve) => {
61
+ PushNotification.requestPermissions((permissions: any) => {
62
+ resolve(permissions.alert === true);
63
+ });
64
+ });
65
+ } else if (Platform.OS === 'android') {
66
+ if (Platform.Version >= 33) {
67
+ const granted = await PermissionsAndroid.request(
68
+ PermissionsAndroid.PERMISSIONS.POST_NOTIFICATIONS
69
+ );
70
+ return granted === PermissionsAndroid.RESULTS.GRANTED;
71
+ }
72
+ return true;
73
+ }
74
+ return false;
75
+ }
76
+
77
+ public async checkPermissions(): Promise<boolean> {
78
+ return new Promise((resolve) => {
79
+ PushNotification.checkPermissions((permissions: any) => {
80
+ resolve(permissions.alert === true || permissions.alert === 1);
81
+ });
82
+ });
83
+ }
84
+
85
+ /**
86
+ * Schedule a trial reminder notification.
87
+ *
88
+ * @param apiKey - @deprecated Resolved from BillDogConfiguration if omitted
89
+ */
90
+ public async scheduleTrialNotification(
91
+ subscription: Subscription,
92
+ config: NotificationConfig,
93
+ apiKey?: string
94
+ ): Promise<void> {
95
+ if (!subscription.isTrial) {
96
+ console.warn('⚠️ Not a trial subscription, skipping notification');
97
+ throw new Error('Not a trial subscription');
98
+ }
99
+
100
+ if (!subscription.expiresAt || new Date(subscription.expiresAt) < new Date()) {
101
+ console.warn('⚠️ Trial already expired, skipping notification');
102
+ throw new Error('Trial already expired');
103
+ }
104
+
105
+ if (!subscription.purchasedAt) {
106
+ console.warn('⚠️ Missing trial start date, skipping notification');
107
+ throw new Error('Missing trial start date');
108
+ }
109
+
110
+ const purchaseDate = new Date(subscription.purchasedAt);
111
+ const notificationDate = new Date(purchaseDate);
112
+ notificationDate.setDate(notificationDate.getDate() + config.dayOfTrial);
113
+
114
+ if (notificationDate < new Date()) {
115
+ console.warn(
116
+ `⚠️ Notification date is in the past (Day ${config.dayOfTrial} was ${notificationDate}), skipping`
117
+ );
118
+ throw new Error('Notification date in the past');
119
+ }
120
+
121
+ if (notificationDate > new Date(subscription.expiresAt)) {
122
+ console.warn('⚠️ Notification date is after trial expiration, skipping');
123
+ throw new Error('Notification date after expiration');
124
+ }
125
+
126
+ if (subscription.notificationScheduledAt) {
127
+ console.warn(
128
+ `⚠️ Notification already scheduled at ${subscription.notificationScheduledAt}`
129
+ );
130
+ throw new Error('Notification already scheduled');
131
+ }
132
+
133
+ const hasPermission = await this.checkPermissions();
134
+ if (!hasPermission) {
135
+ if (config.requestPermissions) {
136
+ const granted = await this.requestPermissions();
137
+ if (!granted) {
138
+ throw new Error('Notification permission denied');
139
+ }
140
+ } else {
141
+ throw new Error('Notification permission not granted');
142
+ }
143
+ }
144
+
145
+ PushNotification.localNotificationSchedule({
146
+ id: `trial_reminder_${subscription.id}`,
147
+ channelId: 'trial-reminders',
148
+ title: config.title,
149
+ message: config.body,
150
+ date: notificationDate,
151
+ allowWhileIdle: true,
152
+ userInfo: {
153
+ subscription_id: subscription.id,
154
+ notification_type: 'trial_reminder',
155
+ day_of_trial: config.dayOfTrial,
156
+ },
157
+ ...(config.subtitle && Platform.OS === 'ios' ? { subtitle: config.subtitle } : {}),
158
+ ...(config.sound ? { soundName: config.sound } : {}),
159
+ ...(config.badge ? { number: config.badge } : {}),
160
+ });
161
+
162
+ // Update subscription via ApiClient
163
+ await this.updateSubscriptionNotificationStatus(
164
+ subscription.id,
165
+ { scheduledAt: new Date() },
166
+ apiKey
167
+ );
168
+
169
+ console.log(
170
+ `✅ Scheduled trial reminder notification for Day ${config.dayOfTrial} at ${notificationDate}`
171
+ );
172
+ }
173
+
174
+ public async cancelNotification(
175
+ subscriptionId: string,
176
+ apiKey?: string
177
+ ): Promise<void> {
178
+ PushNotification.cancelLocalNotification(`trial_reminder_${subscriptionId}`);
179
+
180
+ await this.updateSubscriptionNotificationStatus(
181
+ subscriptionId,
182
+ { cancelledAt: new Date() },
183
+ apiKey
184
+ );
185
+
186
+ console.log('✅ Cancelled trial reminder notification');
187
+ }
188
+
189
+ public async getScheduledNotifications(): Promise<any[]> {
190
+ return new Promise((resolve) => {
191
+ PushNotification.getScheduledLocalNotifications((notifications: any[]) => {
192
+ resolve(notifications);
193
+ });
194
+ });
195
+ }
196
+
197
+ /**
198
+ * Update subscription notification status via ApiClient.
199
+ */
200
+ private async updateSubscriptionNotificationStatus(
201
+ subscriptionId: string,
202
+ updates: {
203
+ scheduledAt?: Date;
204
+ sentAt?: Date;
205
+ cancelledAt?: Date;
206
+ },
207
+ apiKey?: string
208
+ ): Promise<void> {
209
+ const body: Record<string, string> = {};
210
+
211
+ if (updates.scheduledAt) {
212
+ body.notification_scheduled_at = updates.scheduledAt.toISOString();
213
+ }
214
+ if (updates.sentAt) {
215
+ body.notification_sent_at = updates.sentAt.toISOString();
216
+ }
217
+ if (updates.cancelledAt) {
218
+ body.notification_cancelled_at = updates.cancelledAt.toISOString();
219
+ }
220
+
221
+ if (Object.keys(body).length === 0) return;
222
+
223
+ try {
224
+ await ApiClient.post('update-subscription-notification', {
225
+ subscription_id: subscriptionId,
226
+ ...body,
227
+ }, {
228
+ apiKey,
229
+ });
230
+ } catch (error) {
231
+ console.warn('⚠️ Failed to update subscription status:', error);
232
+ }
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Type definitions
238
+ */
239
+ export interface NotificationConfig {
240
+ enabled: boolean;
241
+ dayOfTrial: number;
242
+ title: string;
243
+ subtitle?: string;
244
+ body: string;
245
+ requestPermissions: boolean;
246
+ removeNotificationEnabled?: boolean;
247
+ sound?: string;
248
+ badge?: number;
249
+ }
250
+
251
+ export interface Subscription {
252
+ id: string;
253
+ isTrial: boolean;
254
+ purchasedAt?: string;
255
+ expiresAt?: string;
256
+ notificationScheduledAt?: string;
257
+ notificationSentAt?: string;
258
+ notificationCancelledAt?: string;
259
+ }
260
+
261
+ // Convenience exports
262
+ export const requestNotificationPermissions = () =>
263
+ NotificationScheduler.getInstance().requestPermissions();
264
+
265
+ export const checkNotificationPermissions = () =>
266
+ NotificationScheduler.getInstance().checkPermissions();
267
+
268
+ export const scheduleTrialNotification = (
269
+ subscription: Subscription,
270
+ config: NotificationConfig,
271
+ apiKey?: string
272
+ ) =>
273
+ NotificationScheduler.getInstance().scheduleTrialNotification(
274
+ subscription,
275
+ config,
276
+ apiKey
277
+ );
278
+
279
+ export const cancelTrialNotification = (
280
+ subscriptionId: string,
281
+ apiKey?: string
282
+ ) =>
283
+ NotificationScheduler.getInstance().cancelNotification(
284
+ subscriptionId,
285
+ apiKey
286
+ );
287
+
288
+ export const getScheduledNotifications = () =>
289
+ NotificationScheduler.getInstance().getScheduledNotifications();
290
+
291
+ // Re-export device token registration
292
+ export { useDeviceToken, registerDeviceToken } from './useDeviceToken';
@@ -0,0 +1,67 @@
1
+ import PushNotification from 'react-native-push-notification';
2
+ import { ApiClient } from '../utils/ApiClient';
3
+
4
+ export interface PurchaseNotificationConfig {
5
+ /** @deprecated Use BillDogConfiguration.configure({ apiKey }) instead */
6
+ apiKey?: string;
7
+ }
8
+
9
+ export interface PurchaseDetails {
10
+ subscriptionId: string;
11
+ userId: string;
12
+ projectId: string;
13
+ productId: string;
14
+ platform: 'ios' | 'android';
15
+ }
16
+
17
+ /**
18
+ * Handles notification cancellation when purchases are completed.
19
+ *
20
+ * Resolves API endpoint from BillDogConfiguration by default.
21
+ */
22
+ export class PurchaseNotificationHandler {
23
+ /** @deprecated */
24
+ private apiKey?: string;
25
+
26
+ constructor(config: PurchaseNotificationConfig = {}) {
27
+ this.apiKey = config.apiKey;
28
+ }
29
+
30
+ async handlePurchaseCompleted(details: PurchaseDetails): Promise<boolean> {
31
+ try {
32
+ this.cancelLocalNotification(details.subscriptionId);
33
+ await this.cancelRemoteNotification(details);
34
+ return true;
35
+ } catch (error) {
36
+ console.error('[PurchaseNotificationHandler] Error handling purchase:', error);
37
+ throw error;
38
+ }
39
+ }
40
+
41
+ private cancelLocalNotification(subscriptionId: string): void {
42
+ const notificationId = `trial-reminder-${subscriptionId}`;
43
+ PushNotification.cancelLocalNotification(notificationId);
44
+ PushNotification.cancelAllLocalNotifications();
45
+ console.log('[PurchaseNotificationHandler] Cancelled local notification:', notificationId);
46
+ }
47
+
48
+ private async cancelRemoteNotification(details: PurchaseDetails): Promise<void> {
49
+ try {
50
+ const result = await ApiClient.post('handle-purchase-notification', {
51
+ subscriptionId: details.subscriptionId,
52
+ userId: details.userId,
53
+ projectId: details.projectId,
54
+ productId: details.productId,
55
+ platform: details.platform,
56
+ purchaseDate: new Date().toISOString(),
57
+ }, {
58
+ apiKey: this.apiKey,
59
+ });
60
+
61
+ console.log('[PurchaseNotificationHandler] Successfully updated backend:', result);
62
+ } catch (error) {
63
+ console.error('[PurchaseNotificationHandler] Error updating backend:', error);
64
+ throw error;
65
+ }
66
+ }
67
+ }