@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,385 @@
1
+ "use strict";
2
+ /**
3
+ * xFlow Tag Preprocessor — React Native SDK.
4
+ *
5
+ * Faithful port of src/lib/filters/xFlowTags.ts (the web reference). Implements
6
+ * the canonical grammar at src/schemas/xflow-grammar.md. Runs as a first pass
7
+ * before {{ }} interpolation. The conformance corpus
8
+ * (tests/parity/xflow-corpus/) is the cross-platform contract — web, iOS,
9
+ * Android, and React Native parsers must produce identical output.
10
+ *
11
+ * Tags: {% assign %} (with filter chains), {% if/elsif/else/endif %},
12
+ * {% unless/endunless %}, {% case/when/else/endcase %}.
13
+ * Conditions: ==, !=, >, <, >=, <=, contains, and, or, unary !, postfix exists.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.processXFlowTags = processXFlowTags;
17
+ const valueUtils_1 = require("./valueUtils");
18
+ const filters_1 = require("./filters");
19
+ function processXFlowTags(text, variables) {
20
+ if (!text || typeof text !== 'string' || !text.includes('{%')) {
21
+ return { text: text !== null && text !== void 0 ? text : '', variables };
22
+ }
23
+ const vars = { ...variables };
24
+ const tokens = tokenize(text);
25
+ const output = processTokens(tokens, vars);
26
+ return { text: output, variables: vars };
27
+ }
28
+ function tokenize(text) {
29
+ const tokens = [];
30
+ let pos = 0;
31
+ while (pos < text.length) {
32
+ const tagStart = text.indexOf('{%', pos);
33
+ if (tagStart === -1) {
34
+ tokens.push({ type: 'text', content: text.slice(pos), raw: text.slice(pos) });
35
+ break;
36
+ }
37
+ if (tagStart > pos) {
38
+ const segment = text.slice(pos, tagStart);
39
+ tokens.push({ type: 'text', content: segment, raw: segment });
40
+ }
41
+ const tagEnd = text.indexOf('%}', tagStart);
42
+ if (tagEnd === -1) {
43
+ const segment = text.slice(tagStart);
44
+ tokens.push({ type: 'text', content: segment, raw: segment });
45
+ break;
46
+ }
47
+ const raw = text.slice(tagStart, tagEnd + 2);
48
+ const content = text.slice(tagStart + 2, tagEnd).trim();
49
+ tokens.push({ type: 'tag', content, raw });
50
+ pos = tagEnd + 2;
51
+ }
52
+ return tokens;
53
+ }
54
+ // ============= Token Processor =============
55
+ function processTokens(tokens, vars) {
56
+ let output = '';
57
+ let i = 0;
58
+ while (i < tokens.length) {
59
+ const token = tokens[i];
60
+ if (token.type === 'text') {
61
+ output += token.content;
62
+ i++;
63
+ continue;
64
+ }
65
+ const tagContent = token.content;
66
+ if (tagContent.startsWith('assign ')) {
67
+ processAssign(tagContent, vars);
68
+ i++;
69
+ continue;
70
+ }
71
+ if (tagContent.startsWith('if ')) {
72
+ const { result, endIndex } = processIfBlock(tokens, i, vars);
73
+ output += result;
74
+ i = endIndex + 1;
75
+ continue;
76
+ }
77
+ // {% unless X %}…{% endunless %} — Liquid's negated-if. Rewrite the open
78
+ // token to `if !(X)` and reuse processIfBlock (which also closes on
79
+ // `endunless`). elsif/else keep their if-semantics.
80
+ if (tagContent.startsWith('unless ')) {
81
+ const condition = tagContent.slice('unless '.length).trim();
82
+ const rewritten = tokens.slice();
83
+ rewritten[i] = { type: 'tag', content: `if !(${condition})`, raw: token.raw };
84
+ const { result, endIndex } = processIfBlock(rewritten, i, vars);
85
+ output += result;
86
+ i = endIndex + 1;
87
+ continue;
88
+ }
89
+ // {% case X %}{% when Y %}…{% else %}…{% endcase %}
90
+ if (tagContent.startsWith('case ')) {
91
+ const { result, endIndex } = processCaseBlock(tokens, i, vars);
92
+ output += result;
93
+ i = endIndex + 1;
94
+ continue;
95
+ }
96
+ // Unknown tag — keep raw.
97
+ output += token.raw;
98
+ i++;
99
+ }
100
+ return output;
101
+ }
102
+ // ============= Assign Processing =============
103
+ function processAssign(tagContent, vars) {
104
+ const assignMatch = tagContent.match(/^assign\s+(\w+)\s*=\s*(.+)$/);
105
+ if (!assignMatch)
106
+ return;
107
+ const varName = assignMatch[1];
108
+ const expression = assignMatch[2].trim();
109
+ const { baseExpr, filterChain } = splitExpressionAndFilters(expression);
110
+ let value = resolveValue(baseExpr.trim(), vars);
111
+ if (filterChain) {
112
+ try {
113
+ value = (0, filters_1.applyFilterChain)(value, filterChain);
114
+ }
115
+ catch (error) {
116
+ console.warn(`[xFlowTags] Filter chain failed for assign "${varName}":`, error);
117
+ }
118
+ }
119
+ vars[varName] = value;
120
+ }
121
+ /** Split "value | filter1 | filter2" at the first unquoted pipe. */
122
+ function splitExpressionAndFilters(expression) {
123
+ let inQuote = false;
124
+ let quoteChar = '';
125
+ for (let i = 0; i < expression.length; i++) {
126
+ const char = expression[i];
127
+ if ((char === '"' || char === "'") && expression[i - 1] !== '\\') {
128
+ if (!inQuote) {
129
+ inQuote = true;
130
+ quoteChar = char;
131
+ }
132
+ else if (char === quoteChar) {
133
+ inQuote = false;
134
+ }
135
+ }
136
+ else if (char === '|' && !inQuote) {
137
+ return {
138
+ baseExpr: expression.slice(0, i),
139
+ filterChain: expression.slice(i + 1).trim(),
140
+ };
141
+ }
142
+ }
143
+ return { baseExpr: expression, filterChain: null };
144
+ }
145
+ /**
146
+ * Process an {% if %} / {% unless %} block. `endif` and `endunless` are
147
+ * interchangeable terminators (grammar §2.3).
148
+ */
149
+ function processIfBlock(tokens, startIndex, vars) {
150
+ const openingCondition = tokens[startIndex].content.slice(3).trim(); // remove "if "
151
+ const branches = [];
152
+ let currentCondition = openingCondition;
153
+ let currentBody = [];
154
+ let depth = 0;
155
+ let i = startIndex + 1;
156
+ while (i < tokens.length) {
157
+ const token = tokens[i];
158
+ if (token.type === 'tag') {
159
+ const content = token.content;
160
+ if (content.startsWith('if ') || content.startsWith('unless ')) {
161
+ depth++;
162
+ currentBody.push(token);
163
+ i++;
164
+ continue;
165
+ }
166
+ if (content === 'endif' || content === 'endunless') {
167
+ if (depth > 0) {
168
+ depth--;
169
+ currentBody.push(token);
170
+ i++;
171
+ continue;
172
+ }
173
+ branches.push({ condition: currentCondition, bodyTokens: currentBody });
174
+ break;
175
+ }
176
+ if (depth === 0 && content.startsWith('elsif ')) {
177
+ branches.push({ condition: currentCondition, bodyTokens: currentBody });
178
+ currentCondition = content.slice(6).trim();
179
+ currentBody = [];
180
+ i++;
181
+ continue;
182
+ }
183
+ if (depth === 0 && content === 'else') {
184
+ branches.push({ condition: currentCondition, bodyTokens: currentBody });
185
+ currentCondition = null;
186
+ currentBody = [];
187
+ i++;
188
+ continue;
189
+ }
190
+ }
191
+ currentBody.push(token);
192
+ i++;
193
+ }
194
+ if (i >= tokens.length) {
195
+ branches.push({ condition: currentCondition, bodyTokens: currentBody });
196
+ }
197
+ for (const branch of branches) {
198
+ if (branch.condition === null || evaluateCondition(branch.condition, vars)) {
199
+ return { result: processTokens(branch.bodyTokens, vars), endIndex: i };
200
+ }
201
+ }
202
+ return { result: '', endIndex: i };
203
+ }
204
+ // ============= Case Block Processing =============
205
+ /**
206
+ * Process {% case X %}{% when Y %}…{% when A, B %}…{% else %}…{% endcase %}.
207
+ * Evaluates X once; emits the first `when` whose value (any of its
208
+ * comma-separated candidates) is string-equal to X; falls back to `else`.
209
+ */
210
+ function processCaseBlock(tokens, startIndex, vars) {
211
+ const caseExpr = tokens[startIndex].content.slice('case '.length).trim();
212
+ const caseValue = resolveValue(caseExpr, vars);
213
+ const branches = [];
214
+ let currentCondition = null;
215
+ let currentBody = [];
216
+ let inBranch = false;
217
+ let depth = 0;
218
+ let i = startIndex + 1;
219
+ while (i < tokens.length) {
220
+ const token = tokens[i];
221
+ if (token.type === 'tag') {
222
+ const content = token.content;
223
+ if (content.startsWith('case ')) {
224
+ depth++;
225
+ currentBody.push(token);
226
+ i++;
227
+ continue;
228
+ }
229
+ if (content === 'endcase') {
230
+ if (depth > 0) {
231
+ depth--;
232
+ currentBody.push(token);
233
+ i++;
234
+ continue;
235
+ }
236
+ if (inBranch)
237
+ branches.push({ condition: currentCondition, bodyTokens: currentBody });
238
+ break;
239
+ }
240
+ if (depth === 0 && content.startsWith('when ')) {
241
+ if (inBranch)
242
+ branches.push({ condition: currentCondition, bodyTokens: currentBody });
243
+ currentCondition = content.slice('when '.length).trim();
244
+ currentBody = [];
245
+ inBranch = true;
246
+ i++;
247
+ continue;
248
+ }
249
+ if (depth === 0 && content === 'else') {
250
+ if (inBranch)
251
+ branches.push({ condition: currentCondition, bodyTokens: currentBody });
252
+ currentCondition = null;
253
+ currentBody = [];
254
+ inBranch = true;
255
+ i++;
256
+ continue;
257
+ }
258
+ }
259
+ if (inBranch)
260
+ currentBody.push(token);
261
+ i++;
262
+ }
263
+ if (i >= tokens.length && inBranch) {
264
+ branches.push({ condition: currentCondition, bodyTokens: currentBody });
265
+ }
266
+ const caseStr = (0, valueUtils_1.stringify)(caseValue);
267
+ for (const branch of branches) {
268
+ if (branch.condition === null) {
269
+ // else branch
270
+ return { result: processTokens(branch.bodyTokens, vars), endIndex: i };
271
+ }
272
+ // when supports comma-separated candidates: `when "a", "b"`.
273
+ const candidates = branch.condition.split(',').map((c) => c.trim());
274
+ for (const candidate of candidates) {
275
+ if ((0, valueUtils_1.stringify)(resolveValue(candidate, vars)) === caseStr) {
276
+ return { result: processTokens(branch.bodyTokens, vars), endIndex: i };
277
+ }
278
+ }
279
+ }
280
+ return { result: '', endIndex: i };
281
+ }
282
+ // ============= Condition Evaluation =============
283
+ function evaluateCondition(condition, vars) {
284
+ const trimmed = condition.trim();
285
+ // Unary `!` negation. Strips one optional outer paren pair so `!(a and b)`
286
+ // negates the whole inner condition.
287
+ if (trimmed.startsWith('!')) {
288
+ let inner = trimmed.slice(1).trim();
289
+ if (inner.startsWith('(') && inner.endsWith(')')) {
290
+ inner = inner.slice(1, -1).trim();
291
+ }
292
+ return !evaluateCondition(inner, vars);
293
+ }
294
+ const parts = splitLogical(trimmed);
295
+ if (parts.length === 1) {
296
+ return evaluateComparison(parts[0].expr.trim(), vars);
297
+ }
298
+ let result = evaluateComparison(parts[0].expr.trim(), vars);
299
+ for (let i = 1; i < parts.length; i++) {
300
+ const { operator, expr } = parts[i];
301
+ const right = evaluateComparison(expr.trim(), vars);
302
+ result = operator === 'and' ? result && right : result || right;
303
+ }
304
+ return result;
305
+ }
306
+ function splitLogical(condition) {
307
+ const segments = condition.split(/\b(and|or)\b/);
308
+ const result = [];
309
+ for (let i = 0; i < segments.length; i++) {
310
+ if (i === 0) {
311
+ result.push({ operator: null, expr: segments[0] });
312
+ }
313
+ else if (segments[i] === 'and' || segments[i] === 'or') {
314
+ result.push({ operator: segments[i], expr: segments[i + 1] || '' });
315
+ i++;
316
+ }
317
+ }
318
+ return result.length > 0 ? result : [{ operator: null, expr: condition }];
319
+ }
320
+ /** Evaluate "a == b", "a contains b", "a exists", or bare "a" (truthiness). */
321
+ function evaluateComparison(expr, vars) {
322
+ // Postfix `exists` — checked before binary operators so an identifier
323
+ // containing "exists" isn't mis-parsed.
324
+ if (expr.endsWith(' exists')) {
325
+ const leftStr = expr.slice(0, -' exists'.length).trim();
326
+ if (leftStr) {
327
+ const value = resolveValue(leftStr, vars);
328
+ return value !== null && value !== undefined && (0, valueUtils_1.stringify)(value) !== '';
329
+ }
330
+ }
331
+ const operators = ['!=', '>=', '<=', '==', '>', '<', ' contains '];
332
+ for (const op of operators) {
333
+ const opIndex = expr.indexOf(op);
334
+ if (opIndex !== -1) {
335
+ const left = resolveValue(expr.slice(0, opIndex).trim(), vars);
336
+ const right = resolveValue(expr.slice(opIndex + op.length).trim(), vars);
337
+ return compareValues(left, right, op.trim());
338
+ }
339
+ }
340
+ return (0, valueUtils_1.isTruthy)(resolveValue(expr.trim(), vars));
341
+ }
342
+ function isNullishEqual(a, b) {
343
+ const an = a === null || a === undefined;
344
+ const bn = b === null || b === undefined;
345
+ if (an && bn)
346
+ return true;
347
+ if (an || bn)
348
+ return false;
349
+ return (0, valueUtils_1.stringify)(a) === (0, valueUtils_1.stringify)(b);
350
+ }
351
+ function compareValues(left, right, op) {
352
+ switch (op) {
353
+ case '==': return isNullishEqual(left, right);
354
+ case '!=': return !isNullishEqual(left, right);
355
+ case '>': return (0, valueUtils_1.toNumber)(left) > (0, valueUtils_1.toNumber)(right);
356
+ case '<': return (0, valueUtils_1.toNumber)(left) < (0, valueUtils_1.toNumber)(right);
357
+ case '>=': return (0, valueUtils_1.toNumber)(left) >= (0, valueUtils_1.toNumber)(right);
358
+ case '<=': return (0, valueUtils_1.toNumber)(left) <= (0, valueUtils_1.toNumber)(right);
359
+ case 'contains': {
360
+ if (Array.isArray(left)) {
361
+ return left.some((el) => (0, valueUtils_1.stringify)(el) === (0, valueUtils_1.stringify)(right));
362
+ }
363
+ return (0, valueUtils_1.stringify)(left).includes((0, valueUtils_1.stringify)(right));
364
+ }
365
+ default: return false;
366
+ }
367
+ }
368
+ // ============= Value Resolution =============
369
+ function resolveValue(token, vars) {
370
+ if (!token)
371
+ return undefined;
372
+ if ((token.startsWith('"') && token.endsWith('"')) ||
373
+ (token.startsWith("'") && token.endsWith("'"))) {
374
+ return token.slice(1, -1);
375
+ }
376
+ if (token === 'true')
377
+ return true;
378
+ if (token === 'false')
379
+ return false;
380
+ if (token === 'nil' || token === 'null')
381
+ return null;
382
+ if (/^-?\d+(\.\d+)?$/.test(token))
383
+ return Number(token);
384
+ return (0, valueUtils_1.getNestedValue)(vars, token);
385
+ }
@@ -0,0 +1,50 @@
1
+ //
2
+ // BillDogABTest.m
3
+ // @billdog.io/react-native
4
+ //
5
+ // RCT_EXTERN bridge declarations for the BillDogABTest Swift module.
6
+ // ⚠️ Requires `pod install` + Xcode to compile/verify — no iOS toolchain in
7
+ // the authoring environment.
8
+ //
9
+
10
+ #import <React/RCTBridgeModule.h>
11
+ #import <React/RCTEventEmitter.h>
12
+
13
+ @interface RCT_EXTERN_MODULE(BillDogABTest, RCTEventEmitter)
14
+
15
+ RCT_EXTERN_METHOD(initialize:(RCTPromiseResolveBlock)resolve
16
+ rejecter:(RCTPromiseRejectBlock)reject)
17
+
18
+ RCT_EXTERN_METHOD(getVariant:(NSString *)experimentId
19
+ resolver:(RCTPromiseResolveBlock)resolve
20
+ rejecter:(RCTPromiseRejectBlock)reject)
21
+
22
+ RCT_EXTERN_METHOD(trackConversion:(NSString *)experimentId
23
+ value:(nonnull NSNumber *)value
24
+ metadata:(NSDictionary *)metadata
25
+ resolver:(RCTPromiseResolveBlock)resolve
26
+ rejecter:(RCTPromiseRejectBlock)reject)
27
+
28
+ RCT_EXTERN_METHOD(trackImpression:(NSString *)experimentId
29
+ resolver:(RCTPromiseResolveBlock)resolve
30
+ rejecter:(RCTPromiseRejectBlock)reject)
31
+
32
+ RCT_EXTERN_METHOD(setUserId:(NSString *)id
33
+ resolver:(RCTPromiseResolveBlock)resolve
34
+ rejecter:(RCTPromiseRejectBlock)reject)
35
+
36
+ RCT_EXTERN_METHOD(setAttributes:(NSDictionary *)attributes
37
+ resolver:(RCTPromiseResolveBlock)resolve
38
+ rejecter:(RCTPromiseRejectBlock)reject)
39
+
40
+ RCT_EXTERN_METHOD(getFeatureFlag:(NSString *)key
41
+ resolver:(RCTPromiseResolveBlock)resolve
42
+ rejecter:(RCTPromiseRejectBlock)reject)
43
+
44
+ RCT_EXTERN_METHOD(refreshExperiments:(RCTPromiseResolveBlock)resolve
45
+ rejecter:(RCTPromiseRejectBlock)reject)
46
+
47
+ RCT_EXTERN_METHOD(reset:(RCTPromiseResolveBlock)resolve
48
+ rejecter:(RCTPromiseRejectBlock)reject)
49
+
50
+ @end
@@ -0,0 +1,188 @@
1
+ //
2
+ // BillDogABTest.swift
3
+ // @billdog.io/react-native
4
+ //
5
+ // React Native bridge → native `BillDogABTestManager`.
6
+ //
7
+ // ⚠️ VERIFICATION: there is no iOS/CocoaPods toolchain in the authoring
8
+ // environment. This file is written to be internally consistent and
9
+ // compile-ready against the verified native signatures (cited inline as
10
+ // `BillDogABTest.swift:<line>` / `BillDog.swift:<line>`), but it REQUIRES a
11
+ // `pod install` + Xcode build to actually compile and run.
12
+ //
13
+ // Manager is looked up per-call via `BillDog.shared.getModules()`
14
+ // (BillDog.swift:579). When the ABTest module is not registered (SDK not
15
+ // configured), every call is a best-effort no-op resolving nil/default —
16
+ // matching the JS side, which no-ops when the native module is unlinked.
17
+ //
18
+
19
+ import Foundation
20
+ import React
21
+ import BillDogFull
22
+
23
+ @objc(BillDogABTest)
24
+ class BillDogABTest: RCTEventEmitter {
25
+
26
+ private var hasListeners = false
27
+ private var busSubscriptionId: String?
28
+
29
+ override static func requiresMainQueueSetup() -> Bool {
30
+ return false
31
+ }
32
+
33
+ override func supportedEvents() -> [String]! {
34
+ return ["onExperimentAssignmentChanged"]
35
+ }
36
+
37
+ override func startObserving() {
38
+ hasListeners = true
39
+ // Native emits `ab_variant_assigned` on the in-process EventBus
40
+ // (BillDogABTest.swift:297, BillDogEventBus.swift:42). The bus carries
41
+ // only {experiment_id, variant_id}, so the JS event body is a PARTIAL
42
+ // ExperimentAssignment (variantName/config/isControl are not on the bus).
43
+ busSubscriptionId = BillDogEventBus.shared.subscribe(
44
+ BillDogEventBus.EventTypes.abVariantAssigned
45
+ ) { [weak self] event in
46
+ guard let self = self, self.hasListeners else { return }
47
+ self.sendEvent(withName: "onExperimentAssignmentChanged", body: [
48
+ "experimentId": event.properties["experiment_id"] as? String ?? "",
49
+ "variantId": event.properties["variant_id"] as? String ?? ""
50
+ ])
51
+ }
52
+ }
53
+
54
+ override func stopObserving() {
55
+ hasListeners = false
56
+ if let id = busSubscriptionId {
57
+ BillDogEventBus.shared.unsubscribe(id) // BillDogEventBus.swift:95
58
+ busSubscriptionId = nil
59
+ }
60
+ }
61
+
62
+ // MARK: - Native manager access
63
+
64
+ // BillDog.shared.getModules() -> [BillDogModule] (BillDog.swift:579)
65
+ // BillDogABTestManager: BillDogModule (BillDogABTest.swift:6)
66
+ private func manager() -> BillDogABTestManager? {
67
+ return BillDog.shared.getModules()
68
+ .compactMap { $0 as? BillDogABTestManager }
69
+ .first
70
+ }
71
+
72
+ // MARK: - Lifecycle
73
+
74
+ // No native `initialize()` — the module is configured via BillDog.configure().
75
+ // Resolve as a best-effort no-op so the JS init handshake completes.
76
+ @objc
77
+ func initialize(_ resolve: @escaping RCTPromiseResolveBlock,
78
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
79
+ resolve(nil)
80
+ }
81
+
82
+ // MARK: - Variant assignment
83
+
84
+ @objc
85
+ func getVariant(_ experimentId: String,
86
+ resolver resolve: @escaping RCTPromiseResolveBlock,
87
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
88
+ guard let mgr = manager() else { resolve(nil); return }
89
+ Task {
90
+ // mgr.getVariant(experimentId:) async -> ExperimentAssignment? (BillDogABTest.swift:201)
91
+ // Serialized inline — the ExperimentAssignment type name is ambiguous
92
+ // (declared in two modules), so we never spell it, only read fields.
93
+ if let assignment = await mgr.getVariant(experimentId: experimentId) {
94
+ let config = assignment.config?.mapValues { $0.value } ?? [:]
95
+ resolve([
96
+ "experimentId": assignment.experimentId,
97
+ "variantId": assignment.variantId,
98
+ "variantName": assignment.variantName,
99
+ "config": config,
100
+ "isControl": assignment.isControl
101
+ ])
102
+ } else {
103
+ resolve(nil)
104
+ }
105
+ }
106
+ }
107
+
108
+ // MARK: - Event tracking
109
+
110
+ @objc
111
+ func trackConversion(_ experimentId: String,
112
+ value: NSNumber,
113
+ metadata: NSDictionary,
114
+ resolver resolve: @escaping RCTPromiseResolveBlock,
115
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
116
+ guard let mgr = manager() else { resolve(nil); return }
117
+ // mgr.trackConversion(experimentId:value:metadata:) (BillDogABTest.swift:245)
118
+ mgr.trackConversion(
119
+ experimentId: experimentId,
120
+ value: value.doubleValue,
121
+ metadata: metadata as? [String: Any]
122
+ )
123
+ resolve(nil)
124
+ }
125
+
126
+ @objc
127
+ func trackImpression(_ experimentId: String,
128
+ resolver resolve: @escaping RCTPromiseResolveBlock,
129
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
130
+ guard let mgr = manager() else { resolve(nil); return }
131
+ // mgr.trackImpression(experimentId:) (BillDogABTest.swift:276)
132
+ mgr.trackImpression(experimentId: experimentId)
133
+ resolve(nil)
134
+ }
135
+
136
+ // MARK: - Identity / targeting
137
+
138
+ @objc
139
+ func setUserId(_ id: String,
140
+ resolver resolve: @escaping RCTPromiseResolveBlock,
141
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
142
+ guard let mgr = manager() else { resolve(nil); return }
143
+ // mgr.setUserId(_:) (BillDogABTest.swift:65) — also triggers refresh
144
+ mgr.setUserId(id)
145
+ resolve(nil)
146
+ }
147
+
148
+ @objc
149
+ func setAttributes(_ attributes: NSDictionary,
150
+ resolver resolve: @escaping RCTPromiseResolveBlock,
151
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
152
+ guard let mgr = manager() else { resolve(nil); return }
153
+ // mgr.setAttributes(_:) (BillDogABTest.swift:71)
154
+ mgr.setAttributes(attributes as? [String: Any] ?? [:])
155
+ resolve(nil)
156
+ }
157
+
158
+ // MARK: - Feature flags
159
+
160
+ @objc
161
+ func getFeatureFlag(_ key: String,
162
+ resolver resolve: @escaping RCTPromiseResolveBlock,
163
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
164
+ guard let mgr = manager() else { resolve(nil); return }
165
+ // mgr.getFeatureFlag(key:) -> Bool (BillDogABTest.swift:220)
166
+ resolve(mgr.getFeatureFlag(key: key))
167
+ }
168
+
169
+ // MARK: - Refresh / reset
170
+
171
+ @objc
172
+ func refreshExperiments(_ resolve: @escaping RCTPromiseResolveBlock,
173
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
174
+ guard let mgr = manager() else { resolve(nil); return }
175
+ // mgr.refreshExperiments() (BillDogABTest.swift:191)
176
+ mgr.refreshExperiments()
177
+ resolve(nil)
178
+ }
179
+
180
+ @objc
181
+ func reset(_ resolve: @escaping RCTPromiseResolveBlock,
182
+ rejecter reject: @escaping RCTPromiseRejectBlock) {
183
+ guard let mgr = manager() else { resolve(nil); return }
184
+ // mgr.reset() (BillDogABTest.swift:306)
185
+ mgr.reset()
186
+ resolve(nil)
187
+ }
188
+ }