@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,407 @@
|
|
|
1
|
+
package io.billdog.reactnative
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import com.facebook.react.bridge.Arguments
|
|
5
|
+
import com.facebook.react.bridge.Promise
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
7
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
8
|
+
import com.facebook.react.bridge.ReactMethod
|
|
9
|
+
import com.facebook.react.bridge.ReadableArray
|
|
10
|
+
import com.facebook.react.bridge.ReadableMap
|
|
11
|
+
import com.facebook.react.bridge.ReadableType
|
|
12
|
+
import com.facebook.react.bridge.WritableArray
|
|
13
|
+
import com.facebook.react.bridge.WritableMap
|
|
14
|
+
import com.facebook.react.modules.core.DeviceEventManagerModule
|
|
15
|
+
import kotlinx.coroutines.CoroutineScope
|
|
16
|
+
import kotlinx.coroutines.Dispatchers
|
|
17
|
+
import kotlinx.coroutines.SupervisorJob
|
|
18
|
+
import kotlinx.coroutines.cancel
|
|
19
|
+
import kotlinx.coroutines.launch
|
|
20
|
+
import kotlinx.coroutines.withContext
|
|
21
|
+
import org.json.JSONArray
|
|
22
|
+
import org.json.JSONObject
|
|
23
|
+
import java.io.BufferedReader
|
|
24
|
+
import java.io.InputStreamReader
|
|
25
|
+
import java.net.HttpURLConnection
|
|
26
|
+
import java.net.URL
|
|
27
|
+
import java.net.URLEncoder
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* BillDogVirtualCurrency — React Native native module for Android
|
|
31
|
+
* (`NativeModules.BillDogVirtualCurrency`).
|
|
32
|
+
*
|
|
33
|
+
* Standalone HTTP bridge that mirrors the iOS `BillDogVirtualCurrencyBridge.swift`
|
|
34
|
+
* 1:1 — same endpoints, same request bodies, same `X-BillDog-API-Key` header, and
|
|
35
|
+
* the same `onBalanceChanged` / `onTransaction` events. This is deliberately a
|
|
36
|
+
* self-contained bridge (not a delegation to `io.billdog:billdog-virtual-currency`)
|
|
37
|
+
* so the RN Virtual Currency surface behaves identically on iOS and Android and
|
|
38
|
+
* does not depend on the core module registry being wired with the VC satellite.
|
|
39
|
+
*
|
|
40
|
+
* The authoritative JS contract is `src/modules/VirtualCurrency.ts`; each
|
|
41
|
+
* `BillDogVirtualCurrency.<method>(...)` there maps to the identically-named
|
|
42
|
+
* `@ReactMethod` below.
|
|
43
|
+
*
|
|
44
|
+
* ⚠️ VERIFICATION: this file needs a Gradle/Android build
|
|
45
|
+
* (`./gradlew :billdog_react-native:compileDebugKotlin` or the example app build)
|
|
46
|
+
* to type-check — there is no Android toolchain in the authoring environment.
|
|
47
|
+
*/
|
|
48
|
+
class BillDogVirtualCurrencyModule(private val reactContext: ReactApplicationContext) :
|
|
49
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
50
|
+
|
|
51
|
+
private val TAG = "BillDogVirtualCurrency"
|
|
52
|
+
|
|
53
|
+
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
|
|
54
|
+
|
|
55
|
+
private var baseUrl: String? = null
|
|
56
|
+
private var apiKey: String? = null
|
|
57
|
+
private var customerId: String? = null
|
|
58
|
+
private var enableLogging: Boolean = false
|
|
59
|
+
|
|
60
|
+
override fun getName(): String = "BillDogVirtualCurrency"
|
|
61
|
+
|
|
62
|
+
// ── Lifecycle ────────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
override fun invalidate() {
|
|
65
|
+
scope.cancel()
|
|
66
|
+
super.invalidate()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@Deprecated("Deprecated in RN; kept for older RN versions that call it instead of invalidate()")
|
|
70
|
+
override fun onCatalystInstanceDestroy() {
|
|
71
|
+
scope.cancel()
|
|
72
|
+
@Suppress("DEPRECATION")
|
|
73
|
+
super.onCatalystInstanceDestroy()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// NativeEventEmitter on the JS side requires these to exist on the module.
|
|
77
|
+
@ReactMethod
|
|
78
|
+
fun addListener(@Suppress("UNUSED_PARAMETER") eventName: String) { /* RCTEventEmitter — no-op. */ }
|
|
79
|
+
|
|
80
|
+
@ReactMethod
|
|
81
|
+
fun removeListeners(@Suppress("UNUSED_PARAMETER") count: Double) { /* RCTEventEmitter — no-op. */ }
|
|
82
|
+
|
|
83
|
+
// ── Initialization ───────────────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* BillDogVirtualCurrency.initialize(config) — stores baseUrl / apiKey /
|
|
87
|
+
* customerId / enableLogging. Mirrors the iOS bridge's `initialize`.
|
|
88
|
+
*/
|
|
89
|
+
@ReactMethod
|
|
90
|
+
fun initialize(config: ReadableMap, promise: Promise) {
|
|
91
|
+
val baseUrl = if (config.hasKey("baseUrl")) config.getString("baseUrl") else null
|
|
92
|
+
val apiKey = if (config.hasKey("apiKey")) config.getString("apiKey") else null
|
|
93
|
+
val customerId = if (config.hasKey("customerId")) config.getString("customerId") else null
|
|
94
|
+
if (baseUrl.isNullOrEmpty() || apiKey.isNullOrEmpty() || customerId.isNullOrEmpty()) {
|
|
95
|
+
promise.reject("INVALID_CONFIG", "Missing required configuration parameters")
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
this.baseUrl = baseUrl.trimEnd('/')
|
|
99
|
+
this.apiKey = apiKey
|
|
100
|
+
this.customerId = customerId
|
|
101
|
+
this.enableLogging = config.hasKey("enableLogging") && config.getBoolean("enableLogging")
|
|
102
|
+
if (enableLogging) Log.d(TAG, "Virtual Currency initialized for customer: $customerId")
|
|
103
|
+
promise.resolve(null)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── Balance operations ─────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
@ReactMethod
|
|
109
|
+
fun getBalance(currencyId: String, promise: Promise) {
|
|
110
|
+
val customerId = this.customerId ?: return promise.reject(
|
|
111
|
+
"NOT_INITIALIZED", "Virtual Currency module not initialized"
|
|
112
|
+
)
|
|
113
|
+
run(promise) {
|
|
114
|
+
val q = "currency_id=${enc(currencyId)}&customer_id=${enc(customerId)}"
|
|
115
|
+
val json = httpGet("/virtual-currency/balances?$q")
|
|
116
|
+
json.getDouble("balance")
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@ReactMethod
|
|
121
|
+
fun addCurrency(
|
|
122
|
+
currencyId: String,
|
|
123
|
+
amount: Double,
|
|
124
|
+
reason: String?,
|
|
125
|
+
metadata: ReadableMap?,
|
|
126
|
+
promise: Promise,
|
|
127
|
+
) {
|
|
128
|
+
transaction(currencyId, amount, "earn", reason, metadata, "ADD_CURRENCY_ERROR", promise)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
@ReactMethod
|
|
132
|
+
fun deductCurrency(
|
|
133
|
+
currencyId: String,
|
|
134
|
+
amount: Double,
|
|
135
|
+
reason: String?,
|
|
136
|
+
metadata: ReadableMap?,
|
|
137
|
+
promise: Promise,
|
|
138
|
+
) {
|
|
139
|
+
// Deduct posts a negative amount, mirroring the iOS bridge.
|
|
140
|
+
transaction(currencyId, -amount, "spend", reason, metadata, "DEDUCT_CURRENCY_ERROR", promise)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@ReactMethod
|
|
144
|
+
fun grantCurrency(
|
|
145
|
+
currencyId: String,
|
|
146
|
+
amount: Double,
|
|
147
|
+
reason: String?,
|
|
148
|
+
promise: Promise,
|
|
149
|
+
) {
|
|
150
|
+
transaction(currencyId, amount, "grant", reason, null, "GRANT_CURRENCY_ERROR", promise)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
private fun transaction(
|
|
154
|
+
currencyId: String,
|
|
155
|
+
amount: Double,
|
|
156
|
+
type: String,
|
|
157
|
+
reason: String?,
|
|
158
|
+
metadata: ReadableMap?,
|
|
159
|
+
errorCode: String,
|
|
160
|
+
promise: Promise,
|
|
161
|
+
) {
|
|
162
|
+
val customerId = this.customerId ?: return promise.reject(
|
|
163
|
+
"NOT_INITIALIZED", "Virtual Currency module not initialized"
|
|
164
|
+
)
|
|
165
|
+
scope.launch {
|
|
166
|
+
try {
|
|
167
|
+
val body = JSONObject().apply {
|
|
168
|
+
put("currency_id", currencyId)
|
|
169
|
+
put("customer_id", customerId)
|
|
170
|
+
put("amount", amount)
|
|
171
|
+
put("type", type)
|
|
172
|
+
if (reason != null) put("reason", reason)
|
|
173
|
+
if (metadata != null) put("metadata", readableMapToJson(metadata))
|
|
174
|
+
}
|
|
175
|
+
val json = withContext(Dispatchers.IO) { httpPost("/virtual-currency/transactions", body) }
|
|
176
|
+
val newBalance = json.getDouble("new_balance")
|
|
177
|
+
emit("onBalanceChanged", Arguments.createMap().apply {
|
|
178
|
+
putString("currencyId", currencyId)
|
|
179
|
+
putDouble("newBalance", newBalance)
|
|
180
|
+
})
|
|
181
|
+
emit("onTransaction", Arguments.createMap().apply {
|
|
182
|
+
putString("id", json.optString("id", ""))
|
|
183
|
+
putString("currencyId", currencyId)
|
|
184
|
+
putDouble("amount", amount)
|
|
185
|
+
putString("type", type)
|
|
186
|
+
if (reason != null) putString("reason", reason)
|
|
187
|
+
putDouble("newBalance", newBalance)
|
|
188
|
+
})
|
|
189
|
+
promise.resolve(newBalance)
|
|
190
|
+
} catch (e: Throwable) {
|
|
191
|
+
promise.reject(errorCode, e.message, e)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
@ReactMethod
|
|
197
|
+
fun getTransactionHistory(currencyId: String?, limit: Double, promise: Promise) {
|
|
198
|
+
val customerId = this.customerId ?: return promise.reject(
|
|
199
|
+
"NOT_INITIALIZED", "Virtual Currency module not initialized"
|
|
200
|
+
)
|
|
201
|
+
run(promise) {
|
|
202
|
+
var path = "/virtual-currency/transactions/history?customer_id=${enc(customerId)}&limit=${limit.toInt()}"
|
|
203
|
+
if (!currencyId.isNullOrEmpty()) path += "¤cy_id=${enc(currencyId)}"
|
|
204
|
+
val json = httpGet(path)
|
|
205
|
+
jsonArrayToWritable(json.optJSONArray("transactions") ?: JSONArray())
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
@ReactMethod
|
|
210
|
+
fun exchangeCurrency(
|
|
211
|
+
fromCurrencyId: String,
|
|
212
|
+
toCurrencyId: String,
|
|
213
|
+
fromAmount: Double,
|
|
214
|
+
promise: Promise,
|
|
215
|
+
) {
|
|
216
|
+
val customerId = this.customerId ?: return promise.reject(
|
|
217
|
+
"NOT_INITIALIZED", "Virtual Currency module not initialized"
|
|
218
|
+
)
|
|
219
|
+
scope.launch {
|
|
220
|
+
try {
|
|
221
|
+
val body = JSONObject().apply {
|
|
222
|
+
put("from_currency_id", fromCurrencyId)
|
|
223
|
+
put("to_currency_id", toCurrencyId)
|
|
224
|
+
put("from_amount", fromAmount)
|
|
225
|
+
put("customer_id", customerId)
|
|
226
|
+
}
|
|
227
|
+
val json = withContext(Dispatchers.IO) { httpPost("/virtual-currency/exchange", body) }
|
|
228
|
+
// Build the camelCase ExchangeResult the JS `ExchangeResult` type expects.
|
|
229
|
+
val newFrom = json.optDouble("new_from_balance", 0.0)
|
|
230
|
+
val newTo = json.optDouble("new_to_balance", 0.0)
|
|
231
|
+
val result: WritableMap = Arguments.createMap().apply {
|
|
232
|
+
putBoolean("success", true)
|
|
233
|
+
putString("fromCurrencyId", fromCurrencyId)
|
|
234
|
+
putString("toCurrencyId", toCurrencyId)
|
|
235
|
+
putDouble("fromAmount", fromAmount)
|
|
236
|
+
putDouble("toAmount", json.optDouble("to_amount", 0.0))
|
|
237
|
+
putDouble("exchangeRate", json.optDouble("exchange_rate", 0.0))
|
|
238
|
+
putDouble("newFromBalance", newFrom)
|
|
239
|
+
putDouble("newToBalance", newTo)
|
|
240
|
+
}
|
|
241
|
+
emit("onBalanceChanged", Arguments.createMap().apply {
|
|
242
|
+
putString("currencyId", fromCurrencyId)
|
|
243
|
+
putDouble("newBalance", newFrom)
|
|
244
|
+
})
|
|
245
|
+
emit("onBalanceChanged", Arguments.createMap().apply {
|
|
246
|
+
putString("currencyId", toCurrencyId)
|
|
247
|
+
putDouble("newBalance", newTo)
|
|
248
|
+
})
|
|
249
|
+
promise.resolve(result)
|
|
250
|
+
} catch (e: Throwable) {
|
|
251
|
+
promise.reject("EXCHANGE_ERROR", e.message, e)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
@ReactMethod
|
|
257
|
+
fun getAllCurrencies(promise: Promise) {
|
|
258
|
+
val customerId = this.customerId ?: return promise.reject(
|
|
259
|
+
"NOT_INITIALIZED", "Virtual Currency module not initialized"
|
|
260
|
+
)
|
|
261
|
+
run(promise) {
|
|
262
|
+
// All-balances route returns `data.balances = [{currency:{…}, balance}]`
|
|
263
|
+
// (NOT `currencies`); the JS layer normalizes the nested shape.
|
|
264
|
+
val json = httpGet("/virtual-currency/balances?customer_id=${enc(customerId)}")
|
|
265
|
+
jsonArrayToWritable(json.optJSONArray("balances") ?: JSONArray())
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
270
|
+
|
|
271
|
+
/** Run [block] on the IO dispatcher; resolve/reject the promise. */
|
|
272
|
+
private fun run(promise: Promise, block: () -> Any?) {
|
|
273
|
+
scope.launch {
|
|
274
|
+
try {
|
|
275
|
+
val value = withContext(Dispatchers.IO) { block() }
|
|
276
|
+
promise.resolve(value)
|
|
277
|
+
} catch (e: Throwable) {
|
|
278
|
+
promise.reject("BILLDOG_VC_ERROR", e.message, e)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
private fun emit(name: String, body: WritableMap) {
|
|
284
|
+
try {
|
|
285
|
+
reactContext
|
|
286
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
|
|
287
|
+
.emit(name, body)
|
|
288
|
+
} catch (_: Throwable) { /* no active JS context — drop the event */ }
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private fun enc(s: String): String = URLEncoder.encode(s, "UTF-8")
|
|
292
|
+
|
|
293
|
+
private fun openConnection(path: String, method: String): HttpURLConnection {
|
|
294
|
+
val base = baseUrl ?: throw IllegalStateException("Configuration missing")
|
|
295
|
+
val key = apiKey ?: throw IllegalStateException("Configuration missing")
|
|
296
|
+
val conn = URL(base + path).openConnection() as HttpURLConnection
|
|
297
|
+
conn.requestMethod = method
|
|
298
|
+
conn.setRequestProperty("X-BillDog-API-Key", key)
|
|
299
|
+
conn.setRequestProperty("Content-Type", "application/json")
|
|
300
|
+
conn.setRequestProperty("Accept", "application/json")
|
|
301
|
+
conn.connectTimeout = 15000
|
|
302
|
+
conn.readTimeout = 15000
|
|
303
|
+
return conn
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private fun httpGet(path: String): JSONObject {
|
|
307
|
+
val conn = openConnection(path, "GET")
|
|
308
|
+
return readJson(conn)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private fun httpPost(path: String, body: JSONObject): JSONObject {
|
|
312
|
+
val conn = openConnection(path, "POST")
|
|
313
|
+
conn.doOutput = true
|
|
314
|
+
conn.outputStream.use { it.write(body.toString().toByteArray(Charsets.UTF_8)) }
|
|
315
|
+
return readJson(conn)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private fun readJson(conn: HttpURLConnection): JSONObject {
|
|
319
|
+
try {
|
|
320
|
+
val code = conn.responseCode
|
|
321
|
+
val stream = if (code in 200..299) conn.inputStream else conn.errorStream
|
|
322
|
+
val text = stream?.let {
|
|
323
|
+
BufferedReader(InputStreamReader(it, Charsets.UTF_8)).use { r -> r.readText() }
|
|
324
|
+
} ?: ""
|
|
325
|
+
if (code !in 200..299) {
|
|
326
|
+
throw RuntimeException(if (text.isNotEmpty()) text else "HTTP $code")
|
|
327
|
+
}
|
|
328
|
+
val root = JSONObject(text)
|
|
329
|
+
// BillDog edge functions wrap payloads in { success, data, meta }
|
|
330
|
+
// (supabase/functions/_shared/response.ts). Descend into `data` so
|
|
331
|
+
// callers read `balance`/`transactions`/`new_balance` at the top.
|
|
332
|
+
return root.optJSONObject("data") ?: root
|
|
333
|
+
} finally {
|
|
334
|
+
conn.disconnect()
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
private fun readableMapToJson(map: ReadableMap): JSONObject {
|
|
339
|
+
val json = JSONObject()
|
|
340
|
+
val it = map.keySetIterator()
|
|
341
|
+
while (it.hasNextKey()) {
|
|
342
|
+
val key = it.nextKey()
|
|
343
|
+
when (map.getType(key)) {
|
|
344
|
+
ReadableType.Null -> json.put(key, JSONObject.NULL)
|
|
345
|
+
ReadableType.Boolean -> json.put(key, map.getBoolean(key))
|
|
346
|
+
ReadableType.Number -> json.put(key, map.getDouble(key))
|
|
347
|
+
ReadableType.String -> json.put(key, map.getString(key))
|
|
348
|
+
ReadableType.Map -> json.put(key, readableMapToJson(map.getMap(key)!!))
|
|
349
|
+
ReadableType.Array -> json.put(key, readableArrayToJson(map.getArray(key)!!))
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return json
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
private fun readableArrayToJson(array: ReadableArray): JSONArray {
|
|
356
|
+
val json = JSONArray()
|
|
357
|
+
for (i in 0 until array.size()) {
|
|
358
|
+
when (array.getType(i)) {
|
|
359
|
+
ReadableType.Null -> json.put(JSONObject.NULL)
|
|
360
|
+
ReadableType.Boolean -> json.put(array.getBoolean(i))
|
|
361
|
+
ReadableType.Number -> json.put(array.getDouble(i))
|
|
362
|
+
ReadableType.String -> json.put(array.getString(i))
|
|
363
|
+
ReadableType.Map -> json.put(readableMapToJson(array.getMap(i)))
|
|
364
|
+
ReadableType.Array -> json.put(readableArrayToJson(array.getArray(i)))
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return json
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private fun jsonToWritable(json: JSONObject): WritableMap {
|
|
371
|
+
val map = Arguments.createMap()
|
|
372
|
+
val keys = json.keys()
|
|
373
|
+
while (keys.hasNext()) {
|
|
374
|
+
val key = keys.next()
|
|
375
|
+
when (val v = json.get(key)) {
|
|
376
|
+
JSONObject.NULL -> map.putNull(key)
|
|
377
|
+
is Boolean -> map.putBoolean(key, v)
|
|
378
|
+
is Int -> map.putInt(key, v)
|
|
379
|
+
is Long -> map.putDouble(key, v.toDouble())
|
|
380
|
+
is Double -> map.putDouble(key, v)
|
|
381
|
+
is String -> map.putString(key, v)
|
|
382
|
+
is JSONObject -> map.putMap(key, jsonToWritable(v))
|
|
383
|
+
is JSONArray -> map.putArray(key, jsonArrayToWritable(v))
|
|
384
|
+
else -> map.putString(key, v.toString())
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return map
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private fun jsonArrayToWritable(array: JSONArray): WritableArray {
|
|
391
|
+
val out = Arguments.createArray()
|
|
392
|
+
for (i in 0 until array.length()) {
|
|
393
|
+
when (val v = array.get(i)) {
|
|
394
|
+
JSONObject.NULL -> out.pushNull()
|
|
395
|
+
is Boolean -> out.pushBoolean(v)
|
|
396
|
+
is Int -> out.pushInt(v)
|
|
397
|
+
is Long -> out.pushDouble(v.toDouble())
|
|
398
|
+
is Double -> out.pushDouble(v)
|
|
399
|
+
is String -> out.pushString(v)
|
|
400
|
+
is JSONObject -> out.pushMap(jsonToWritable(v))
|
|
401
|
+
is JSONArray -> out.pushArray(jsonArrayToWritable(v))
|
|
402
|
+
else -> out.pushString(v.toString())
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return out
|
|
406
|
+
}
|
|
407
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
package io.billdog.virtualcurrency
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
import io.billdog.reactnative.BillDogModule
|
|
8
|
+
import io.billdog.reactnative.BillDogAnalyticsModule
|
|
9
|
+
import io.billdog.reactnative.BillDogABTestModule
|
|
10
|
+
import io.billdog.reactnative.BillDogSurveyModule
|
|
11
|
+
import io.billdog.reactnative.BillDogVirtualCurrencyModule
|
|
12
|
+
import io.billdog.reactnative.BillDogPaywallViewManager
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* ReactPackage for @billdog.io/react-native (Android).
|
|
16
|
+
*
|
|
17
|
+
* Thin-bridge architecture: the modules and view managers registered here delegate to
|
|
18
|
+
* the native BillDog Android SDK (io.billdog:billdog-core / billdog-compose) rather than
|
|
19
|
+
* reimplementing logic in JS.
|
|
20
|
+
*
|
|
21
|
+
* Phase 0 (this commit): module skeleton so autolinking resolves and the package compiles.
|
|
22
|
+
* Phase 2 registers the real native modules and the paywall view manager below.
|
|
23
|
+
*/
|
|
24
|
+
class BillDogVirtualCurrencyPackage : ReactPackage {
|
|
25
|
+
|
|
26
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
27
|
+
// Thin-bridge native modules — each delegates to the native BillDog Android SDK,
|
|
28
|
+
// except BillDogVirtualCurrency which is a self-contained HTTP bridge mirroring
|
|
29
|
+
// the iOS BillDogVirtualCurrencyBridge.swift 1:1 (iOS/Android RN parity).
|
|
30
|
+
return listOf(
|
|
31
|
+
BillDogModule(reactContext), // NativeModules.BillDog (core, 41 methods)
|
|
32
|
+
BillDogAnalyticsModule(reactContext), // NativeModules.BillDogAnalytics
|
|
33
|
+
BillDogABTestModule(reactContext), // NativeModules.BillDogABTest
|
|
34
|
+
BillDogSurveyModule(reactContext), // NativeModules.BillDogSurvey
|
|
35
|
+
BillDogVirtualCurrencyModule(reactContext), // NativeModules.BillDogVirtualCurrency
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
40
|
+
// BDPaywallView — SimpleViewManager<FrameLayout> hosting a ComposeView +
|
|
41
|
+
// PaywallScreen(config) for inline (embedded) native paywalls.
|
|
42
|
+
return listOf(BillDogPaywallViewManager())
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 4: Advanced Caching Features for React Native
|
|
3
|
+
* - Cache compression (reduce storage by 60%)
|
|
4
|
+
* - Multi-level caching (memory + disk)
|
|
5
|
+
* - Intelligent prefetching
|
|
6
|
+
* - Analytics tracking
|
|
7
|
+
*/
|
|
8
|
+
export interface AdvancedCacheConfig {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
maxCacheAge: number;
|
|
11
|
+
compressionEnabled: boolean;
|
|
12
|
+
memoryCache: boolean;
|
|
13
|
+
maxMemoryEntries: number;
|
|
14
|
+
intelligentPrefetch: boolean;
|
|
15
|
+
analyticsEnabled: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare const DEFAULT_ADVANCED_CONFIG: AdvancedCacheConfig;
|
|
18
|
+
interface CacheAnalytics {
|
|
19
|
+
hits: number;
|
|
20
|
+
misses: number;
|
|
21
|
+
memoryHits: number;
|
|
22
|
+
diskHits: number;
|
|
23
|
+
compressionSavings: number;
|
|
24
|
+
averageRetrievalTime: number;
|
|
25
|
+
prefetchSuccessRate: number;
|
|
26
|
+
}
|
|
27
|
+
interface CachedPaywall {
|
|
28
|
+
paywall: any;
|
|
29
|
+
version: number;
|
|
30
|
+
cachedAt: number;
|
|
31
|
+
expiresAt: number;
|
|
32
|
+
etag?: string;
|
|
33
|
+
}
|
|
34
|
+
export declare class AdvancedPaywallCache {
|
|
35
|
+
private config;
|
|
36
|
+
private cacheDir;
|
|
37
|
+
private memoryCache;
|
|
38
|
+
private accessPatterns;
|
|
39
|
+
private analytics;
|
|
40
|
+
constructor(config?: Partial<AdvancedCacheConfig>);
|
|
41
|
+
private compressData;
|
|
42
|
+
private decompressData;
|
|
43
|
+
get(identifier: string): Promise<CachedPaywall | null>;
|
|
44
|
+
private getDiskCached;
|
|
45
|
+
set(identifier: string, paywall: CachedPaywall): Promise<void>;
|
|
46
|
+
private addToMemoryCache;
|
|
47
|
+
private findLeastRecentlyUsed;
|
|
48
|
+
private trackAccess;
|
|
49
|
+
private schedulePrefetch;
|
|
50
|
+
learnRelationship(fromId: string, toId: string): void;
|
|
51
|
+
private updateAverageRetrievalTime;
|
|
52
|
+
private saveAnalytics;
|
|
53
|
+
private loadAnalytics;
|
|
54
|
+
getAnalytics(): CacheAnalytics & {
|
|
55
|
+
hitRate: number;
|
|
56
|
+
memoryHitRate: number;
|
|
57
|
+
memorySize: number;
|
|
58
|
+
};
|
|
59
|
+
resetAnalytics(): void;
|
|
60
|
+
private saveAccessPatterns;
|
|
61
|
+
private loadAccessPatterns;
|
|
62
|
+
delete(identifier: string): Promise<void>;
|
|
63
|
+
clearAll(): Promise<void>;
|
|
64
|
+
getStats(): Promise<{
|
|
65
|
+
paywallCount: number;
|
|
66
|
+
memoryCount: number;
|
|
67
|
+
compressionSavings: number;
|
|
68
|
+
}>;
|
|
69
|
+
}
|
|
70
|
+
export {};
|