@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.
- package/BillDogReactNative.podspec +30 -0
- package/LICENSE +21 -0
- package/README.md +330 -0
- package/android/build.gradle +99 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogABTestModule.kt +317 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogAnalyticsModule.kt +389 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogModule.kt +817 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogPaywallViewManager.kt +383 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogSurveyModule.kt +427 -0
- package/android/src/main/kotlin/io/billdog/reactnative/BillDogVirtualCurrencyModule.kt +407 -0
- package/android/src/main/kotlin/io/billdog/virtualcurrency/BillDogVirtualCurrencyPackage.kt +44 -0
- package/dist/AdvancedPaywallCache.d.ts +70 -0
- package/dist/AdvancedPaywallCache.js +344 -0
- package/dist/BillDog.d.ts +255 -0
- package/dist/BillDog.js +918 -0
- package/dist/BillDogConfiguration.d.ts +145 -0
- package/dist/BillDogConfiguration.js +229 -0
- package/dist/ComponentRenderer.d.ts +17 -0
- package/dist/ComponentRenderer.js +886 -0
- package/dist/LayoutEngine.d.ts +101 -0
- package/dist/LayoutEngine.js +394 -0
- package/dist/LocalizationResolver.d.ts +99 -0
- package/dist/LocalizationResolver.js +266 -0
- package/dist/NativePaywallView.d.ts +36 -0
- package/dist/NativePaywallView.js +59 -0
- package/dist/PaywallCache.d.ts +74 -0
- package/dist/PaywallCache.js +293 -0
- package/dist/PaywallStateManager.d.ts +62 -0
- package/dist/PaywallStateManager.js +147 -0
- package/dist/PaywallView.d.ts +42 -0
- package/dist/PaywallView.js +275 -0
- package/dist/ProductVariableProvider.d.ts +60 -0
- package/dist/ProductVariableProvider.js +239 -0
- package/dist/VariableResolver.d.ts +120 -0
- package/dist/VariableResolver.js +379 -0
- package/dist/WebViewPaywall.d.ts +75 -0
- package/dist/WebViewPaywall.js +360 -0
- package/dist/animations/presets.d.ts +49 -0
- package/dist/animations/presets.js +367 -0
- package/dist/components/AccoladeCpsComponent.d.ts +13 -0
- package/dist/components/AccoladeCpsComponent.js +248 -0
- package/dist/components/AccordionComponent.d.ts +10 -0
- package/dist/components/AccordionComponent.js +187 -0
- package/dist/components/AnimatedWrapper.d.ts +35 -0
- package/dist/components/AnimatedWrapper.js +277 -0
- package/dist/components/CarouselComponent.d.ts +23 -0
- package/dist/components/CarouselComponent.js +484 -0
- package/dist/components/ChartCurveComponent.d.ts +13 -0
- package/dist/components/ChartCurveComponent.js +333 -0
- package/dist/components/CheckboxComponent.d.ts +9 -0
- package/dist/components/CheckboxComponent.js +172 -0
- package/dist/components/ConfettiComponent.d.ts +9 -0
- package/dist/components/ConfettiComponent.js +172 -0
- package/dist/components/CountdownComponent.d.ts +16 -0
- package/dist/components/CountdownComponent.js +414 -0
- package/dist/components/DelayedXComponent.d.ts +12 -0
- package/dist/components/DelayedXComponent.js +240 -0
- package/dist/components/DrawerModalComponent.d.ts +20 -0
- package/dist/components/DrawerModalComponent.js +286 -0
- package/dist/components/GradientView.d.ts +38 -0
- package/dist/components/GradientView.js +126 -0
- package/dist/components/HapticFeedbackComponent.d.ts +16 -0
- package/dist/components/HapticFeedbackComponent.js +64 -0
- package/dist/components/HorizontalProductsComponent.d.ts +17 -0
- package/dist/components/HorizontalProductsComponent.js +284 -0
- package/dist/components/MultiChoiceGridComponent.d.ts +13 -0
- package/dist/components/MultiChoiceGridComponent.js +234 -0
- package/dist/components/NPSScaleComponent.d.ts +16 -0
- package/dist/components/NPSScaleComponent.js +260 -0
- package/dist/components/NavigationComponent.d.ts +26 -0
- package/dist/components/NavigationComponent.js +302 -0
- package/dist/components/OAuthButtonComponent.d.ts +20 -0
- package/dist/components/OAuthButtonComponent.js +237 -0
- package/dist/components/ProgressLoaderComponent.d.ts +13 -0
- package/dist/components/ProgressLoaderComponent.js +308 -0
- package/dist/components/ProgressStepperComponent.d.ts +18 -0
- package/dist/components/ProgressStepperComponent.js +228 -0
- package/dist/components/SegmentedPickerComponent.d.ts +10 -0
- package/dist/components/SegmentedPickerComponent.js +221 -0
- package/dist/components/ShareButtonsComponent.d.ts +21 -0
- package/dist/components/ShareButtonsComponent.js +265 -0
- package/dist/components/ShareCodeComponent.d.ts +16 -0
- package/dist/components/ShareCodeComponent.js +274 -0
- package/dist/components/SingleChoiceGridComponent.d.ts +13 -0
- package/dist/components/SingleChoiceGridComponent.js +210 -0
- package/dist/components/SliderComponent.d.ts +9 -0
- package/dist/components/SliderComponent.js +263 -0
- package/dist/components/StarRatingInputComponent.d.ts +20 -0
- package/dist/components/StarRatingInputComponent.js +198 -0
- package/dist/components/StickerCompositeComponent.d.ts +13 -0
- package/dist/components/StickerCompositeComponent.js +136 -0
- package/dist/components/TabsComponent.d.ts +19 -0
- package/dist/components/TabsComponent.js +431 -0
- package/dist/components/TextInputComponent.d.ts +18 -0
- package/dist/components/TextInputComponent.js +227 -0
- package/dist/components/TimelineComponent.d.ts +19 -0
- package/dist/components/TimelineComponent.js +104 -0
- package/dist/components/ToggleComponent.d.ts +14 -0
- package/dist/components/ToggleComponent.js +132 -0
- package/dist/components/VerticalProductsComponent.d.ts +17 -0
- package/dist/components/VerticalProductsComponent.js +311 -0
- package/dist/dynamicValues.d.ts +36 -0
- package/dist/dynamicValues.js +142 -0
- package/dist/filters/dateFilters.d.ts +50 -0
- package/dist/filters/dateFilters.js +390 -0
- package/dist/filters/index.d.ts +18 -0
- package/dist/filters/index.js +253 -0
- package/dist/hooks/useOfferings.d.ts +76 -0
- package/dist/hooks/useOfferings.js +99 -0
- package/dist/hooks/usePaywall.d.ts +27 -0
- package/dist/hooks/usePaywall.js +114 -0
- package/dist/hooks/usePlacement.d.ts +34 -0
- package/dist/hooks/usePlacement.js +89 -0
- package/dist/hooks/useSubscription.d.ts +40 -0
- package/dist/hooks/useSubscription.js +97 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.js +198 -0
- package/dist/models/BillDogError.d.ts +40 -0
- package/dist/models/BillDogError.js +11 -0
- package/dist/models/CacheFetchPolicy.d.ts +8 -0
- package/dist/models/CacheFetchPolicy.js +4 -0
- package/dist/models/CustomProduct.d.ts +32 -0
- package/dist/models/CustomProduct.js +15 -0
- package/dist/models/CustomerInfo.d.ts +17 -0
- package/dist/models/CustomerInfo.js +11 -0
- package/dist/models/Delegate.d.ts +20 -0
- package/dist/models/Delegate.js +11 -0
- package/dist/models/EntitlementInfo.d.ts +19 -0
- package/dist/models/EntitlementInfo.js +2 -0
- package/dist/models/Placement.d.ts +54 -0
- package/dist/models/Placement.js +15 -0
- package/dist/models/PurchaseParams.d.ts +39 -0
- package/dist/models/PurchaseParams.js +60 -0
- package/dist/models/PurchaseResult.d.ts +30 -0
- package/dist/models/PurchaseResult.js +15 -0
- package/dist/models/RestoreResult.d.ts +30 -0
- package/dist/models/RestoreResult.js +22 -0
- package/dist/models/RestoredItem.d.ts +26 -0
- package/dist/models/RestoredItem.js +13 -0
- package/dist/modules/ABTest.d.ts +65 -0
- package/dist/modules/ABTest.js +113 -0
- package/dist/modules/Analytics.d.ts +91 -0
- package/dist/modules/Analytics.js +182 -0
- package/dist/modules/Survey.d.ts +65 -0
- package/dist/modules/Survey.js +82 -0
- package/dist/modules/VirtualCurrency.d.ts +63 -0
- package/dist/modules/VirtualCurrency.js +147 -0
- package/dist/murmur.d.ts +23 -0
- package/dist/murmur.js +104 -0
- package/dist/notifications/LiveActivitiesManager.d.ts +47 -0
- package/dist/notifications/LiveActivitiesManager.js +106 -0
- package/dist/notifications/NotificationScheduler.d.ts +55 -0
- package/dist/notifications/NotificationScheduler.js +199 -0
- package/dist/notifications/PurchaseNotificationHandler.d.ts +24 -0
- package/dist/notifications/PurchaseNotificationHandler.js +55 -0
- package/dist/notifications/useDeviceToken.d.ts +36 -0
- package/dist/notifications/useDeviceToken.js +97 -0
- package/dist/notifications/useLiveActivity.d.ts +39 -0
- package/dist/notifications/useLiveActivity.js +104 -0
- package/dist/review/ReviewAnalytics.d.ts +34 -0
- package/dist/review/ReviewAnalytics.js +73 -0
- package/dist/review/ReviewManager.d.ts +83 -0
- package/dist/review/ReviewManager.js +339 -0
- package/dist/review/ReviewTimingManager.d.ts +82 -0
- package/dist/review/ReviewTimingManager.js +224 -0
- package/dist/survey-core/types.d.ts +215 -0
- package/dist/survey-core/types.js +8 -0
- package/dist/types.d.ts +438 -0
- package/dist/types.js +158 -0
- package/dist/utils/ApiClient.d.ts +65 -0
- package/dist/utils/ApiClient.js +132 -0
- package/dist/utils/CSSConverter.d.ts +49 -0
- package/dist/utils/CSSConverter.js +470 -0
- package/dist/utils/animations.d.ts +41 -0
- package/dist/utils/animations.js +194 -0
- package/dist/utils/calendarIntegration.d.ts +36 -0
- package/dist/utils/calendarIntegration.js +89 -0
- package/dist/utils/configurationResolver.d.ts +23 -0
- package/dist/utils/configurationResolver.js +78 -0
- package/dist/utils/deviceDetection.d.ts +38 -0
- package/dist/utils/deviceDetection.js +59 -0
- package/dist/utils/hapticFeedback.d.ts +17 -0
- package/dist/utils/hapticFeedback.js +53 -0
- package/dist/utils/iconMapper.d.ts +26 -0
- package/dist/utils/iconMapper.js +113 -0
- package/dist/utils/notificationPermissions.d.ts +13 -0
- package/dist/utils/notificationPermissions.js +51 -0
- package/dist/utils/priceFormatting.d.ts +19 -0
- package/dist/utils/priceFormatting.js +99 -0
- package/dist/utils/shareSheet.d.ts +17 -0
- package/dist/utils/shareSheet.js +42 -0
- package/dist/utils/subscriptionUpgrade.d.ts +84 -0
- package/dist/utils/subscriptionUpgrade.js +100 -0
- package/dist/valueUtils.d.ts +48 -0
- package/dist/valueUtils.js +129 -0
- package/dist/xFlowTags.d.ts +18 -0
- package/dist/xFlowTags.js +385 -0
- package/ios/BillDogABTest.m +50 -0
- package/ios/BillDogABTest.swift +188 -0
- package/ios/BillDogAnalytics.m +105 -0
- package/ios/BillDogAnalytics.swift +324 -0
- package/ios/BillDogBridge.m +222 -0
- package/ios/BillDogBridge.swift +715 -0
- package/ios/BillDogLiveActivities.m +41 -0
- package/ios/BillDogLiveActivities.swift +104 -0
- package/ios/BillDogPaywallViewManager.m +39 -0
- package/ios/BillDogPaywallViewManager.swift +361 -0
- package/ios/BillDogSurvey.m +34 -0
- package/ios/BillDogSurvey.swift +257 -0
- package/ios/BillDogVirtualCurrencyBridge.m +63 -0
- package/ios/BillDogVirtualCurrencyBridge.swift +511 -0
- package/junit.xml +25 -0
- package/package.json +61 -0
- package/react-native.config.js +13 -0
- package/src/AdvancedPaywallCache.ts +433 -0
- package/src/BillDog.ts +983 -0
- package/src/BillDogConfiguration.ts +318 -0
- package/src/ComponentRenderer.tsx +1325 -0
- package/src/LayoutEngine.ts +459 -0
- package/src/LocalizationResolver.ts +311 -0
- package/src/NativePaywallView.tsx +132 -0
- package/src/PaywallCache.ts +358 -0
- package/src/PaywallStateManager.ts +154 -0
- package/src/PaywallView.tsx +328 -0
- package/src/ProductVariableProvider.ts +282 -0
- package/src/VariableResolver.ts +516 -0
- package/src/WebViewPaywall.tsx +454 -0
- package/src/animations/presets.ts +421 -0
- package/src/components/AccoladeCpsComponent.tsx +259 -0
- package/src/components/AccordionComponent.tsx +213 -0
- package/src/components/AnimatedWrapper.tsx +316 -0
- package/src/components/CarouselComponent.tsx +615 -0
- package/src/components/ChartCurveComponent.tsx +452 -0
- package/src/components/CheckboxComponent.tsx +185 -0
- package/src/components/ConfettiComponent.tsx +178 -0
- package/src/components/CountdownComponent.tsx +454 -0
- package/src/components/DelayedXComponent.tsx +266 -0
- package/src/components/DrawerModalComponent.tsx +324 -0
- package/src/components/GradientView.tsx +129 -0
- package/src/components/HapticFeedbackComponent.tsx +39 -0
- package/src/components/HorizontalProductsComponent.tsx +329 -0
- package/src/components/MultiChoiceGridComponent.tsx +265 -0
- package/src/components/NPSScaleComponent.tsx +294 -0
- package/src/components/NavigationComponent.tsx +329 -0
- package/src/components/OAuthButtonComponent.tsx +266 -0
- package/src/components/ProgressLoaderComponent.tsx +366 -0
- package/src/components/ProgressStepperComponent.tsx +250 -0
- package/src/components/SegmentedPickerComponent.tsx +245 -0
- package/src/components/ShareButtonsComponent.tsx +299 -0
- package/src/components/ShareCodeComponent.tsx +303 -0
- package/src/components/SingleChoiceGridComponent.tsx +229 -0
- package/src/components/SliderComponent.tsx +280 -0
- package/src/components/StarRatingInputComponent.tsx +223 -0
- package/src/components/StickerCompositeComponent.tsx +168 -0
- package/src/components/TabsComponent.tsx +496 -0
- package/src/components/TextInputComponent.tsx +258 -0
- package/src/components/TimelineComponent.tsx +129 -0
- package/src/components/ToggleComponent.tsx +142 -0
- package/src/components/VerticalProductsComponent.tsx +357 -0
- package/src/dynamicValues.ts +180 -0
- package/src/filters/dateFilters.ts +411 -0
- package/src/filters/index.ts +281 -0
- package/src/hooks/useOfferings.ts +192 -0
- package/src/hooks/usePaywall.ts +154 -0
- package/src/hooks/usePlacement.ts +143 -0
- package/src/hooks/useSubscription.ts +154 -0
- package/src/index.ts +245 -0
- package/src/models/BillDogError.ts +24 -0
- package/src/models/CacheFetchPolicy.ts +13 -0
- package/src/models/CustomProduct.ts +33 -0
- package/src/models/CustomerInfo.ts +26 -0
- package/src/models/Delegate.ts +22 -0
- package/src/models/EntitlementInfo.ts +27 -0
- package/src/models/Placement.ts +50 -0
- package/src/models/PurchaseParams.ts +81 -0
- package/src/models/PurchaseResult.ts +32 -0
- package/src/models/RestoreResult.ts +27 -0
- package/src/models/RestoredItem.ts +25 -0
- package/src/modules/ABTest.ts +131 -0
- package/src/modules/Analytics.ts +227 -0
- package/src/modules/Survey.ts +126 -0
- package/src/modules/VirtualCurrency.ts +223 -0
- package/src/murmur.ts +102 -0
- package/src/notifications/LiveActivitiesManager.ts +140 -0
- package/src/notifications/NotificationScheduler.ts +292 -0
- package/src/notifications/PurchaseNotificationHandler.ts +67 -0
- package/src/notifications/useDeviceToken.ts +116 -0
- package/src/notifications/useLiveActivity.ts +125 -0
- package/src/review/ReviewAnalytics.ts +89 -0
- package/src/review/ReviewManager.ts +355 -0
- package/src/review/ReviewTimingManager.ts +265 -0
- package/src/survey-core/types.ts +244 -0
- package/src/types/ambient.d.ts +9 -0
- package/src/types.ts +680 -0
- package/src/utils/ApiClient.ts +163 -0
- package/src/utils/CSSConverter.ts +552 -0
- package/src/utils/animations.ts +232 -0
- package/src/utils/calendarIntegration.ts +113 -0
- package/src/utils/configurationResolver.ts +106 -0
- package/src/utils/deviceDetection.ts +83 -0
- package/src/utils/hapticFeedback.ts +55 -0
- package/src/utils/iconMapper.ts +102 -0
- package/src/utils/notificationPermissions.ts +48 -0
- package/src/utils/priceFormatting.ts +119 -0
- package/src/utils/shareSheet.ts +55 -0
- package/src/utils/subscriptionUpgrade.ts +183 -0
- package/src/valueUtils.ts +108 -0
- 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
|
+
}
|