@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,163 @@
1
+ /**
2
+ * Unified API Client for BillDog React Native SDK
3
+ *
4
+ * Centralizes all `fetch()` calls to BillDog edge functions with:
5
+ * - Automatic `api_key` injection into every JSON body
6
+ * - Endpoint resolution via `BillDogConfiguration.resolveEndpoint()`
7
+ * - Consistent `Content-Type: application/json` header
8
+ * - Error handling with structured exceptions
9
+ *
10
+ * This eliminates the previous fragmentation where 7 files independently
11
+ * constructed URLs, resolved auth, and called `fetch()` with inconsistent
12
+ * header/body patterns.
13
+ */
14
+
15
+ import { BillDogConfiguration } from '../BillDogConfiguration';
16
+
17
+ export interface ApiClientResponse<T = any> {
18
+ data: T;
19
+ status: number;
20
+ }
21
+
22
+ export class ApiClientError extends Error {
23
+ public readonly status: number;
24
+ public readonly responseBody: any;
25
+
26
+ constructor(message: string, status: number, responseBody?: any) {
27
+ super(message);
28
+ this.name = 'ApiClientError';
29
+ this.status = status;
30
+ this.responseBody = responseBody;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Resolve endpoint URL — delegates to `BillDogConfiguration.resolveEndpoint()`
36
+ * (the single branded base, `https://api.billdog.io/v1`).
37
+ */
38
+ function resolveEndpoint(functionName: string): string {
39
+ return BillDogConfiguration.resolveEndpoint(functionName);
40
+ }
41
+
42
+ /**
43
+ * Worker-less fallback endpoint — `direct.billdog.io/functions/v1/<fn>`.
44
+ * `direct.billdog.io` is a Supabase custom domain with NO Cloudflare Worker,
45
+ * so it uses the native Supabase `/functions/v1/` path (not the Worker's
46
+ * `/v1/` alias). Used when `api.billdog.io` is unreachable.
47
+ */
48
+ function directEndpoint(functionName: string): string {
49
+ return `https://direct.billdog.io/functions/v1/${functionName}`;
50
+ }
51
+
52
+ /**
53
+ * Resolve the API key.
54
+ * If a deprecated `apiKey` override is provided, it takes precedence.
55
+ * Otherwise reads from `BillDogConfiguration`.
56
+ */
57
+ function resolveApiKey(apiKeyOverride?: string): string | undefined {
58
+ if (apiKeyOverride) return apiKeyOverride;
59
+ if (BillDogConfiguration.isConfigured()) {
60
+ return BillDogConfiguration.getApiKey();
61
+ }
62
+ return undefined;
63
+ }
64
+
65
+ export const ApiClient = {
66
+ /**
67
+ * POST to a BillDog edge function.
68
+ *
69
+ * - Injects `api_key` into the JSON body (standard auth contract).
70
+ * - Resolves endpoint via configuration.
71
+ * - Throws `ApiClientError` on non-2xx responses.
72
+ *
73
+ * @param functionName - Edge function name (e.g. 'fetch-paywall')
74
+ * @param body - Request body (api_key will be injected automatically)
75
+ * @param options - Optional overrides for backward compatibility
76
+ */
77
+ async post<T = any>(
78
+ functionName: string,
79
+ body: Record<string, any> = {},
80
+ options?: {
81
+ /** @deprecated Use BillDogConfiguration.configure({ apiKey }) */
82
+ apiKey?: string;
83
+ }
84
+ ): Promise<T> {
85
+ const apiKey = resolveApiKey(options?.apiKey);
86
+
87
+ const fullBody = {
88
+ ...body,
89
+ ...(apiKey ? { api_key: apiKey } : {}),
90
+ };
91
+ const init: RequestInit = {
92
+ method: 'POST',
93
+ headers: {
94
+ 'Content-Type': 'application/json',
95
+ },
96
+ body: JSON.stringify(fullBody),
97
+ };
98
+
99
+ let response: Response;
100
+ try {
101
+ response = await fetch(resolveEndpoint(functionName), init);
102
+ // A gateway error means the api.billdog.io edge / Worker is unhealthy —
103
+ // retry once against the Worker-less direct path.
104
+ if (response.status === 502 || response.status === 503 || response.status === 504) {
105
+ try {
106
+ response = await fetch(directEndpoint(functionName), init);
107
+ } catch {
108
+ /* keep the gateway response — surfaced as the error below */
109
+ }
110
+ }
111
+ } catch {
112
+ // Transport failure — api.billdog.io is unreachable (Cloudflare Worker
113
+ // outage). Fall back to direct.billdog.io (plain DNS → Supabase, no Worker).
114
+ response = await fetch(directEndpoint(functionName), init);
115
+ }
116
+
117
+ if (!response.ok) {
118
+ const errorBody = await response.json().catch(() => ({ error: response.statusText }));
119
+ throw new ApiClientError(
120
+ errorBody.error || `Request to ${functionName} failed: ${response.statusText}`,
121
+ response.status,
122
+ errorBody
123
+ );
124
+ }
125
+
126
+ return response.json();
127
+ },
128
+
129
+ /**
130
+ * GET request to a raw URL (e.g. version check endpoints).
131
+ * Does NOT inject api_key — use only for unauthenticated endpoints.
132
+ */
133
+ async get<T = any>(url: string): Promise<T> {
134
+ const response = await fetch(url, {
135
+ method: 'GET',
136
+ headers: {
137
+ 'Content-Type': 'application/json',
138
+ },
139
+ });
140
+
141
+ if (!response.ok) {
142
+ const errorBody = await response.json().catch(() => ({ error: response.statusText }));
143
+ throw new ApiClientError(
144
+ `GET ${url} failed: ${response.statusText}`,
145
+ response.status,
146
+ errorBody
147
+ );
148
+ }
149
+
150
+ return response.json();
151
+ },
152
+
153
+ /**
154
+ * Convenience: resolve an API key for external use (e.g. hooks that need
155
+ * to check if a key is available before calling).
156
+ */
157
+ resolveApiKey,
158
+
159
+ /**
160
+ * Convenience: resolve an endpoint URL for external use.
161
+ */
162
+ resolveEndpoint,
163
+ };
@@ -0,0 +1,552 @@
1
+ /**
2
+ * CSS to React Native Style Converter
3
+ * Converts CSS strings to React Native StyleSheet-compatible objects
4
+ * Enhanced with gradient detection and unified style generation
5
+ */
6
+
7
+ import { ViewStyle, TextStyle, ImageStyle, DimensionValue } from 'react-native';
8
+
9
+ export interface GradientStop {
10
+ color: string;
11
+ position: number;
12
+ }
13
+
14
+ export interface GradientConfig {
15
+ type: 'linear' | 'radial';
16
+ angle?: number;
17
+ stops: GradientStop[];
18
+ }
19
+
20
+ export interface CSSConversionResult {
21
+ style: ViewStyle | TextStyle | ImageStyle;
22
+ warnings: string[];
23
+ /** If true, the component requires a LinearGradient wrapper */
24
+ requiresGradient?: boolean;
25
+ /** Gradient configuration for LinearGradient component */
26
+ gradient?: GradientConfig;
27
+ /** Fallback solid color if gradient can't be rendered */
28
+ fallbackColor?: string;
29
+ }
30
+
31
+ /**
32
+ * Properties that only work in WebView, not native React Native
33
+ */
34
+ const WEBVIEW_ONLY_PROPERTIES = [
35
+ 'backdrop-filter',
36
+ 'box-shadow', // Partially supported via shadow* props
37
+ 'text-shadow',
38
+ 'background-image',
39
+ 'clip-path',
40
+ 'mask',
41
+ 'filter',
42
+ 'mix-blend-mode',
43
+ 'background-blend-mode',
44
+ ];
45
+
46
+ /**
47
+ * Parse gradient from CSS background property
48
+ */
49
+ function parseGradientFromCSS(value: string): GradientConfig | null {
50
+ // Match linear-gradient(angle, color1 pos1, color2 pos2, ...)
51
+ const linearMatch = value.match(/linear-gradient\(([^)]+)\)/);
52
+ if (linearMatch) {
53
+ const parts = linearMatch[1].split(',').map(p => p.trim());
54
+ let angle = 180;
55
+ let colorStops: string[] = [];
56
+
57
+ // First part might be angle
58
+ if (parts[0].includes('deg')) {
59
+ angle = parseFloat(parts[0].replace('deg', ''));
60
+ colorStops = parts.slice(1);
61
+ } else if (parts[0].startsWith('to ')) {
62
+ // Handle direction keywords
63
+ const dir = parts[0].replace('to ', '');
64
+ if (dir === 'right') angle = 90;
65
+ else if (dir === 'left') angle = 270;
66
+ else if (dir === 'bottom') angle = 180;
67
+ else if (dir === 'top') angle = 0;
68
+ colorStops = parts.slice(1);
69
+ } else {
70
+ colorStops = parts;
71
+ }
72
+
73
+ const stops: GradientStop[] = colorStops.map((stop, index) => {
74
+ const match = stop.match(/(.+?)\s+(\d+)%?$/);
75
+ if (match) {
76
+ return { color: match[1].trim(), position: parseFloat(match[2]) };
77
+ }
78
+ // Default position based on index
79
+ return {
80
+ color: stop.trim(),
81
+ position: (index / Math.max(colorStops.length - 1, 1)) * 100
82
+ };
83
+ });
84
+
85
+ return { type: 'linear', angle, stops };
86
+ }
87
+
88
+ // Match radial-gradient
89
+ const radialMatch = value.match(/radial-gradient\(([^)]+)\)/);
90
+ if (radialMatch) {
91
+ const parts = radialMatch[1].split(',').map(p => p.trim());
92
+ // Skip shape/size specification, get color stops
93
+ const colorStops = parts.filter(p => !p.includes('circle') && !p.includes('ellipse'));
94
+
95
+ const stops: GradientStop[] = colorStops.map((stop, index) => {
96
+ const match = stop.match(/(.+?)\s+(\d+)%?$/);
97
+ if (match) {
98
+ return { color: match[1].trim(), position: parseFloat(match[2]) };
99
+ }
100
+ return {
101
+ color: stop.trim(),
102
+ position: (index / Math.max(colorStops.length - 1, 1)) * 100
103
+ };
104
+ });
105
+
106
+ return { type: 'radial', stops };
107
+ }
108
+
109
+ return null;
110
+ }
111
+
112
+ /**
113
+ * Convert CSS string to React Native styles
114
+ * Now with gradient detection and extraction
115
+ */
116
+ export function convertCSSToReactNative(css: string): CSSConversionResult {
117
+ const style: ViewStyle = {};
118
+ const warnings: string[] = [];
119
+ let requiresGradient = false;
120
+ let gradient: GradientConfig | undefined;
121
+ let fallbackColor: string | undefined;
122
+
123
+ if (!css || css.trim() === '') {
124
+ return { style, warnings };
125
+ }
126
+
127
+ const properties = parseCSSProperties(css);
128
+
129
+ for (const [property, value] of Object.entries(properties)) {
130
+ // Check for gradient in background property
131
+ if (property === 'background' || property === 'background-image') {
132
+ const gradientConfig = parseGradientFromCSS(value);
133
+ if (gradientConfig) {
134
+ requiresGradient = true;
135
+ gradient = gradientConfig;
136
+ fallbackColor = gradientConfig.stops[0]?.color || 'transparent';
137
+ style.backgroundColor = fallbackColor; // Fallback
138
+ continue;
139
+ }
140
+ }
141
+
142
+ // Check for unsupported properties
143
+ if (WEBVIEW_ONLY_PROPERTIES.some(p => property.includes(p))) {
144
+ warnings.push(`'${property}' is not supported in native rendering`);
145
+ continue;
146
+ }
147
+
148
+ processProperty(property, value, style, warnings);
149
+ }
150
+
151
+ return { style, warnings, requiresGradient, gradient, fallbackColor };
152
+ }
153
+
154
+ /**
155
+ * Parse CSS string into property map
156
+ */
157
+ function parseCSSProperties(css: string): Record<string, string> {
158
+ const properties: Record<string, string> = {};
159
+
160
+ const declarations = css.split(';').filter(d => d.trim());
161
+ for (const decl of declarations) {
162
+ const colonIndex = decl.indexOf(':');
163
+ if (colonIndex > 0) {
164
+ const prop = decl.substring(0, colonIndex).trim();
165
+ const value = decl.substring(colonIndex + 1).trim();
166
+ properties[prop] = value;
167
+ }
168
+ }
169
+
170
+ return properties;
171
+ }
172
+
173
+ /**
174
+ * Process a single CSS property
175
+ */
176
+ function processProperty(
177
+ property: string,
178
+ value: string,
179
+ style: ViewStyle,
180
+ warnings: string[]
181
+ ): void {
182
+ const numericValue = parseNumericValue(value);
183
+
184
+ switch (property) {
185
+ // Background
186
+ case 'background-color':
187
+ style.backgroundColor = value;
188
+ break;
189
+
190
+ // Opacity
191
+ case 'opacity':
192
+ style.opacity = parseFloat(value);
193
+ break;
194
+
195
+ // Border radius
196
+ case 'border-radius':
197
+ style.borderRadius = numericValue;
198
+ break;
199
+ case 'border-top-left-radius':
200
+ style.borderTopLeftRadius = numericValue;
201
+ break;
202
+ case 'border-top-right-radius':
203
+ style.borderTopRightRadius = numericValue;
204
+ break;
205
+ case 'border-bottom-left-radius':
206
+ style.borderBottomLeftRadius = numericValue;
207
+ break;
208
+ case 'border-bottom-right-radius':
209
+ style.borderBottomRightRadius = numericValue;
210
+ break;
211
+
212
+ // Border
213
+ case 'border-width':
214
+ style.borderWidth = numericValue;
215
+ break;
216
+ case 'border-color':
217
+ style.borderColor = value;
218
+ break;
219
+ case 'border-style':
220
+ style.borderStyle = value as 'solid' | 'dotted' | 'dashed';
221
+ break;
222
+ case 'border-top-width':
223
+ style.borderTopWidth = numericValue;
224
+ break;
225
+ case 'border-right-width':
226
+ style.borderRightWidth = numericValue;
227
+ break;
228
+ case 'border-bottom-width':
229
+ style.borderBottomWidth = numericValue;
230
+ break;
231
+ case 'border-left-width':
232
+ style.borderLeftWidth = numericValue;
233
+ break;
234
+
235
+ // Padding
236
+ case 'padding':
237
+ applySpacingShorthand(value, style, 'padding');
238
+ break;
239
+ case 'padding-top':
240
+ style.paddingTop = numericValue;
241
+ break;
242
+ case 'padding-right':
243
+ style.paddingRight = numericValue;
244
+ break;
245
+ case 'padding-bottom':
246
+ style.paddingBottom = numericValue;
247
+ break;
248
+ case 'padding-left':
249
+ style.paddingLeft = numericValue;
250
+ break;
251
+
252
+ // Margin
253
+ case 'margin':
254
+ applySpacingShorthand(value, style, 'margin');
255
+ break;
256
+ case 'margin-top':
257
+ style.marginTop = numericValue;
258
+ break;
259
+ case 'margin-right':
260
+ style.marginRight = numericValue;
261
+ break;
262
+ case 'margin-bottom':
263
+ style.marginBottom = numericValue;
264
+ break;
265
+ case 'margin-left':
266
+ style.marginLeft = numericValue;
267
+ break;
268
+
269
+ // Dimensions
270
+ case 'width':
271
+ style.width = parseNumericOrPercent(value);
272
+ break;
273
+ case 'height':
274
+ style.height = parseNumericOrPercent(value);
275
+ break;
276
+ case 'min-width':
277
+ style.minWidth = parseNumericOrPercent(value);
278
+ break;
279
+ case 'min-height':
280
+ style.minHeight = parseNumericOrPercent(value);
281
+ break;
282
+ case 'max-width':
283
+ style.maxWidth = parseNumericOrPercent(value);
284
+ break;
285
+ case 'max-height':
286
+ style.maxHeight = parseNumericOrPercent(value);
287
+ break;
288
+
289
+ // Transform
290
+ case 'transform':
291
+ // parseTransform returns a dynamically-built array of single-key transform
292
+ // objects from parsed CSS. RN's transform union (RotateTransform | ... |
293
+ // MatrixTransform) is impractical to satisfy from dynamic parsing, so cast.
294
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
295
+ style.transform = parseTransform(value) as any;
296
+ break;
297
+
298
+ // Flexbox
299
+ case 'flex':
300
+ style.flex = parseFloat(value);
301
+ break;
302
+ case 'flex-direction':
303
+ style.flexDirection = value as 'row' | 'column' | 'row-reverse' | 'column-reverse';
304
+ break;
305
+ case 'justify-content':
306
+ style.justifyContent = mapJustifyContent(value);
307
+ break;
308
+ case 'align-items':
309
+ style.alignItems = mapAlignItems(value);
310
+ break;
311
+ case 'align-self':
312
+ style.alignSelf = mapAlignItems(value);
313
+ break;
314
+ case 'flex-wrap':
315
+ style.flexWrap = value as 'wrap' | 'nowrap' | 'wrap-reverse';
316
+ break;
317
+
318
+ // Shadow (partial support)
319
+ case 'box-shadow':
320
+ applyBoxShadow(value, style);
321
+ break;
322
+
323
+ // Position
324
+ case 'position':
325
+ style.position = value as 'absolute' | 'relative';
326
+ break;
327
+ case 'top':
328
+ style.top = numericValue;
329
+ break;
330
+ case 'right':
331
+ style.right = numericValue;
332
+ break;
333
+ case 'bottom':
334
+ style.bottom = numericValue;
335
+ break;
336
+ case 'left':
337
+ style.left = numericValue;
338
+ break;
339
+ case 'z-index':
340
+ style.zIndex = parseInt(value);
341
+ break;
342
+
343
+ // Overflow
344
+ case 'overflow':
345
+ style.overflow = value as 'visible' | 'hidden' | 'scroll';
346
+ break;
347
+
348
+ default:
349
+ // Unknown property - add warning
350
+ if (!property.startsWith('-webkit') && !property.startsWith('-moz')) {
351
+ warnings.push(`Unknown CSS property: '${property}'`);
352
+ }
353
+ }
354
+ }
355
+
356
+ /**
357
+ * Parse numeric value from CSS
358
+ */
359
+ function parseNumericValue(value: string): number {
360
+ return parseFloat(value.replace(/px|dp|pt|rem|em/g, '')) || 0;
361
+ }
362
+
363
+ /**
364
+ * Parse numeric or percentage value
365
+ */
366
+ function parseNumericOrPercent(value: string): DimensionValue {
367
+ if (value.includes('%')) {
368
+ // RN's DimensionValue percent type is the template literal `${number}%`;
369
+ // a runtime-parsed CSS string can't be statically proven to match, so cast.
370
+ return value.trim() as DimensionValue;
371
+ }
372
+ return parseNumericValue(value);
373
+ }
374
+
375
+ /**
376
+ * Apply spacing shorthand (padding/margin)
377
+ */
378
+ function applySpacingShorthand(
379
+ value: string,
380
+ style: ViewStyle,
381
+ prefix: 'padding' | 'margin'
382
+ ): void {
383
+ const parts = value.split(/\s+/).map(parseNumericValue);
384
+
385
+ switch (parts.length) {
386
+ case 1:
387
+ (style as any)[prefix] = parts[0];
388
+ break;
389
+ case 2:
390
+ (style as any)[`${prefix}Vertical`] = parts[0];
391
+ (style as any)[`${prefix}Horizontal`] = parts[1];
392
+ break;
393
+ case 3:
394
+ (style as any)[`${prefix}Top`] = parts[0];
395
+ (style as any)[`${prefix}Horizontal`] = parts[1];
396
+ (style as any)[`${prefix}Bottom`] = parts[2];
397
+ break;
398
+ case 4:
399
+ (style as any)[`${prefix}Top`] = parts[0];
400
+ (style as any)[`${prefix}Right`] = parts[1];
401
+ (style as any)[`${prefix}Bottom`] = parts[2];
402
+ (style as any)[`${prefix}Left`] = parts[3];
403
+ break;
404
+ }
405
+ }
406
+
407
+ /**
408
+ * Parse CSS transform to React Native transform array
409
+ */
410
+ function parseTransform(value: string): Array<{ [key: string]: number | string }> {
411
+ const transforms: Array<{ [key: string]: number | string }> = [];
412
+
413
+ // translateX
414
+ const translateXMatch = value.match(/translateX\(([^)]+)\)/);
415
+ if (translateXMatch) {
416
+ transforms.push({ translateX: parseNumericValue(translateXMatch[1]) });
417
+ }
418
+
419
+ // translateY
420
+ const translateYMatch = value.match(/translateY\(([^)]+)\)/);
421
+ if (translateYMatch) {
422
+ transforms.push({ translateY: parseNumericValue(translateYMatch[1]) });
423
+ }
424
+
425
+ // translate(x, y)
426
+ const translateMatch = value.match(/translate\(([^,)]+)(?:,\s*([^)]+))?\)/);
427
+ if (translateMatch) {
428
+ transforms.push({ translateX: parseNumericValue(translateMatch[1]) });
429
+ if (translateMatch[2]) {
430
+ transforms.push({ translateY: parseNumericValue(translateMatch[2]) });
431
+ }
432
+ }
433
+
434
+ // scale
435
+ const scaleMatch = value.match(/scale\(([^)]+)\)/);
436
+ if (scaleMatch) {
437
+ transforms.push({ scale: parseFloat(scaleMatch[1]) });
438
+ }
439
+
440
+ // scaleX
441
+ const scaleXMatch = value.match(/scaleX\(([^)]+)\)/);
442
+ if (scaleXMatch) {
443
+ transforms.push({ scaleX: parseFloat(scaleXMatch[1]) });
444
+ }
445
+
446
+ // scaleY
447
+ const scaleYMatch = value.match(/scaleY\(([^)]+)\)/);
448
+ if (scaleYMatch) {
449
+ transforms.push({ scaleY: parseFloat(scaleYMatch[1]) });
450
+ }
451
+
452
+ // rotate
453
+ const rotateMatch = value.match(/rotate\(([^)]+)\)/);
454
+ if (rotateMatch) {
455
+ const deg = rotateMatch[1].replace('deg', '');
456
+ transforms.push({ rotate: `${deg}deg` });
457
+ }
458
+
459
+ return transforms;
460
+ }
461
+
462
+ /**
463
+ * Apply box-shadow (partial React Native support)
464
+ */
465
+ function applyBoxShadow(value: string, style: ViewStyle): void {
466
+ // Parse: offsetX offsetY blurRadius color
467
+ const match = value.match(/(-?\d+)px\s+(-?\d+)px\s+(-?\d+)px(?:\s+(-?\d+)px)?\s+(.+)/);
468
+ if (match) {
469
+ const [, x, y, blur, _spread, color] = match;
470
+
471
+ style.shadowColor = color.trim();
472
+ style.shadowOffset = { width: parseInt(x), height: parseInt(y) };
473
+ style.shadowOpacity = 1;
474
+ style.shadowRadius = parseInt(blur);
475
+ // Android elevation
476
+ (style as any).elevation = Math.max(parseInt(blur), 1);
477
+ }
478
+ }
479
+
480
+ /**
481
+ * Map CSS justify-content to React Native
482
+ */
483
+ function mapJustifyContent(value: string): ViewStyle['justifyContent'] {
484
+ const map: Record<string, ViewStyle['justifyContent']> = {
485
+ 'flex-start': 'flex-start',
486
+ 'flex-end': 'flex-end',
487
+ 'center': 'center',
488
+ 'space-between': 'space-between',
489
+ 'space-around': 'space-around',
490
+ 'space-evenly': 'space-evenly',
491
+ };
492
+ return map[value] || 'flex-start';
493
+ }
494
+
495
+ /**
496
+ * Map CSS align-items to React Native
497
+ */
498
+ function mapAlignItems(value: string): ViewStyle['alignItems'] {
499
+ const map: Record<string, ViewStyle['alignItems']> = {
500
+ 'flex-start': 'flex-start',
501
+ 'flex-end': 'flex-end',
502
+ 'center': 'center',
503
+ 'stretch': 'stretch',
504
+ 'baseline': 'baseline',
505
+ };
506
+ return map[value] || 'stretch';
507
+ }
508
+
509
+ /**
510
+ * Validate CSS for native compatibility
511
+ */
512
+ export function validateCSSForNative(css: string): string[] {
513
+ const warnings: string[] = [];
514
+
515
+ for (const prop of WEBVIEW_ONLY_PROPERTIES) {
516
+ if (css.includes(prop)) {
517
+ warnings.push(`'${prop}' is only supported in WebView mode`);
518
+ }
519
+ }
520
+
521
+ // Check for gradients (now supported with LinearGradient component)
522
+ if (css.includes('linear-gradient') || css.includes('radial-gradient')) {
523
+ warnings.push('Gradients require LinearGradient component wrapper');
524
+ }
525
+
526
+ return warnings;
527
+ }
528
+
529
+ /**
530
+ * Convert gradient angle to LinearGradient start/end points
531
+ * CSS angle: 0deg = bottom to top, 90deg = left to right
532
+ * LinearGradient: start/end are {x, y} where 0,0 is top-left
533
+ */
534
+ export function angleToGradientPoints(angle: number): {
535
+ start: { x: number; y: number };
536
+ end: { x: number; y: number };
537
+ } {
538
+ const radians = ((angle - 90) * Math.PI) / 180;
539
+
540
+ const startX = 0.5 - Math.cos(radians) * 0.5;
541
+ const startY = 0.5 + Math.sin(radians) * 0.5;
542
+ const endX = 0.5 + Math.cos(radians) * 0.5;
543
+ const endY = 0.5 - Math.sin(radians) * 0.5;
544
+
545
+ return {
546
+ start: { x: startX, y: startY },
547
+ end: { x: endX, y: endY },
548
+ };
549
+ }
550
+
551
+ // GradientStop and GradientConfig are already exported at their `export interface`
552
+ // declarations above; the redundant re-export was removed to avoid a conflict.