@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,454 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebViewPaywall — Optional WebView-based paywall component for the
|
|
3
|
+
* React Native SDK.
|
|
4
|
+
*
|
|
5
|
+
* The default React Native paywall path is native (`PaywallView` →
|
|
6
|
+
* `ComponentRenderer`). This component is the WebView alternative that
|
|
7
|
+
* loads the same unified renderer bundle iOS / Android / Flutter use,
|
|
8
|
+
* so paywalls visually match across all platforms at the cost of one
|
|
9
|
+
* extra `react-native-webview` dependency.
|
|
10
|
+
*
|
|
11
|
+
* Android parity reference:
|
|
12
|
+
* - `BillDogWebViewPaywall` (Android, billdog-paywalls-webview module)
|
|
13
|
+
* - Canonical message protocol:
|
|
14
|
+
* packages/paywall-renderer/src/native/messageProtocol.ts
|
|
15
|
+
* - postMessage events handled here mirror the protocol's
|
|
16
|
+
* OutboundMessageType union: purchase / restore / close / open_url /
|
|
17
|
+
* track / state_changed / error / ready.
|
|
18
|
+
*
|
|
19
|
+
* Architecture choice: URL-based loading (same as Flutter) rather than
|
|
20
|
+
* bundled HTML asset. The renderer is served by the `render-paywall`
|
|
21
|
+
* edge function at `https://api.billdog.io/v1/render-paywall?...`,
|
|
22
|
+
* and the paywall config is delivered to the renderer via the JS
|
|
23
|
+
* bridge function `window.loadPaywallData(...)` — same contract as
|
|
24
|
+
* the Flutter SDK uses (packages/flutter/lib/src/widgets/paywall_view.dart).
|
|
25
|
+
*
|
|
26
|
+
* Bundling the HTML as a static asset requires a metro.config.js change
|
|
27
|
+
* (`assetExts: ['html', ...]` + `metro-html-loader`). URL-based avoids
|
|
28
|
+
* that and is the simpler MVP. If first-paint latency becomes a problem,
|
|
29
|
+
* a future revision can switch to local-asset loading.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import React, { useRef, useEffect, useState, useCallback } from 'react';
|
|
33
|
+
import { View, ActivityIndicator, Text, StyleSheet, Modal } from 'react-native';
|
|
34
|
+
import type { WebViewMessageEvent } from 'react-native-webview';
|
|
35
|
+
import { BillDogConfiguration } from './BillDogConfiguration';
|
|
36
|
+
|
|
37
|
+
// Lazily resolve the `react-native-webview` module. Importing it at the top
|
|
38
|
+
// level runs its native-module lookup (`TurboModuleRegistry.getEnforcing(
|
|
39
|
+
// 'RNCWebViewModule')`) at SDK-load time, which THROWS an Invariant Violation
|
|
40
|
+
// and crashes the host app when the peer isn't fully linked (e.g. old-arch
|
|
41
|
+
// RN 0.73 quirk) — even for apps using only the NATIVE paywall path. Deferring
|
|
42
|
+
// the require to render time keeps `import '@billdog.io/react-native'` safe.
|
|
43
|
+
let _WebView: any;
|
|
44
|
+
function loadWebView(): any {
|
|
45
|
+
if (_WebView === undefined) {
|
|
46
|
+
try {
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
48
|
+
_WebView = require('react-native-webview').default ?? null;
|
|
49
|
+
} catch {
|
|
50
|
+
_WebView = null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return _WebView;
|
|
54
|
+
}
|
|
55
|
+
import { LocalizationResolver } from './LocalizationResolver';
|
|
56
|
+
import { BillDog } from './BillDog';
|
|
57
|
+
|
|
58
|
+
export interface WebViewPaywallProps {
|
|
59
|
+
/** Paywall identifier — matches the placement id used elsewhere. */
|
|
60
|
+
paywallIdentifier: string;
|
|
61
|
+
/** API key for the BillDog project. */
|
|
62
|
+
apiKey: string;
|
|
63
|
+
/** Optional current user id; forwarded to the renderer so it can
|
|
64
|
+
* resolve per-user variants / experiments. */
|
|
65
|
+
customerId?: string;
|
|
66
|
+
/** Render the WebView inside a fullscreen modal. Default true. */
|
|
67
|
+
modal?: boolean;
|
|
68
|
+
/** Visibility (only meaningful when `modal=true`). */
|
|
69
|
+
visible?: boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Explicit locale for the rendered paywall. When omitted the component
|
|
72
|
+
* resolves the effective locale via `LocalizationResolver.getEffectiveLocale()`
|
|
73
|
+
* (runtime override > device locale > "en") — matching the Android
|
|
74
|
+
* `WebViewPaywallActivity.effectiveLocale()` precedence.
|
|
75
|
+
*/
|
|
76
|
+
locale?: string;
|
|
77
|
+
|
|
78
|
+
/** Fires when the renderer reports `ready` over the postMessage bridge. */
|
|
79
|
+
onReady?: () => void;
|
|
80
|
+
/** Fires when the consumer should initiate a purchase. The product id
|
|
81
|
+
* comes from the renderer's `purchase` message. */
|
|
82
|
+
onPurchase?: (productIdentifier: string) => void;
|
|
83
|
+
/** Fires when the user taps Restore. */
|
|
84
|
+
onRestore?: () => void;
|
|
85
|
+
/** Fires when the user closes the paywall. */
|
|
86
|
+
onDismiss?: () => void;
|
|
87
|
+
/** Fires on `open_url` messages (e.g. terms / privacy links). */
|
|
88
|
+
onOpenUrl?: (url: string) => void;
|
|
89
|
+
/** Fires on `track` analytics events. */
|
|
90
|
+
onTrackEvent?: (eventName: string, properties?: Record<string, unknown>) => void;
|
|
91
|
+
/** Fires on any error message from the renderer. */
|
|
92
|
+
onError?: (error: Error) => void;
|
|
93
|
+
/**
|
|
94
|
+
* Optional custom action execution handler (Route B).
|
|
95
|
+
* If provided, the consumer must execute the action and return a promise
|
|
96
|
+
* resolving with the result or rejecting with an error. If omitted, the
|
|
97
|
+
* SDK automatically executes it via the server-side Edge function.
|
|
98
|
+
*/
|
|
99
|
+
onExecuteAction?: (
|
|
100
|
+
paywallId: string | null,
|
|
101
|
+
componentId: string,
|
|
102
|
+
actionType: string,
|
|
103
|
+
idempotencyKey: string | null,
|
|
104
|
+
) => Promise<unknown>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Mirrors the BilldogMessage shape from
|
|
109
|
+
* packages/paywall-renderer/src/native/messageProtocol.ts. Kept inline
|
|
110
|
+
* here so the RN SDK doesn't need a workspace dep on the renderer
|
|
111
|
+
* package — only the wire shape matters.
|
|
112
|
+
*/
|
|
113
|
+
interface BilldogMessage {
|
|
114
|
+
protocol: 'billdog';
|
|
115
|
+
version: number;
|
|
116
|
+
id: string;
|
|
117
|
+
timestamp: number;
|
|
118
|
+
type: string;
|
|
119
|
+
payload: Record<string, unknown>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export const WebViewPaywall: React.FC<WebViewPaywallProps> = ({
|
|
123
|
+
paywallIdentifier,
|
|
124
|
+
apiKey,
|
|
125
|
+
customerId,
|
|
126
|
+
modal = true,
|
|
127
|
+
visible = true,
|
|
128
|
+
locale,
|
|
129
|
+
onReady,
|
|
130
|
+
onPurchase,
|
|
131
|
+
onRestore,
|
|
132
|
+
onDismiss,
|
|
133
|
+
onOpenUrl,
|
|
134
|
+
onTrackEvent,
|
|
135
|
+
onError,
|
|
136
|
+
onExecuteAction,
|
|
137
|
+
}) => {
|
|
138
|
+
const webViewRef = useRef<any>(null);
|
|
139
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
140
|
+
const [errorState, setErrorState] = useState<Error | null>(null);
|
|
141
|
+
|
|
142
|
+
// Resolve the effective locale once per render-input change. Precedence
|
|
143
|
+
// mirrors Android `WebViewPaywallActivity.effectiveLocale()`:
|
|
144
|
+
// explicit prop > runtime override > device locale > "en".
|
|
145
|
+
const effectiveLocale = React.useMemo(
|
|
146
|
+
() => locale?.trim() || LocalizationResolver.getEffectiveLocale(),
|
|
147
|
+
[locale],
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// Build the renderer URL once per identifier/customer/locale change.
|
|
151
|
+
// `locale` is seeded as a query param so the renderer resolves the
|
|
152
|
+
// correct language on first paint (no flash of base-locale text) —
|
|
153
|
+
// the seed-before-load half of the Android bridge contract.
|
|
154
|
+
const rendererUrl = React.useMemo(() => {
|
|
155
|
+
const base = BillDogConfiguration.resolveEndpoint('render-paywall');
|
|
156
|
+
const params = new URLSearchParams({
|
|
157
|
+
paywall_identifier: paywallIdentifier,
|
|
158
|
+
api_key: apiKey,
|
|
159
|
+
});
|
|
160
|
+
if (customerId) params.set('customer_id', customerId);
|
|
161
|
+
if (effectiveLocale) params.set('locale', effectiveLocale);
|
|
162
|
+
return `${base}?${params.toString()}`;
|
|
163
|
+
}, [paywallIdentifier, apiKey, customerId, effectiveLocale]);
|
|
164
|
+
|
|
165
|
+
// Reset state when the identifier changes (consumer may swap paywalls).
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
setIsLoading(true);
|
|
168
|
+
setErrorState(null);
|
|
169
|
+
}, [paywallIdentifier]);
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Push `config:locale` into the renderer via a canonical `state_set`
|
|
173
|
+
* envelope. Mirrors Android `WebViewPaywallActivity.sendLocaleUpdate`
|
|
174
|
+
* (kt:1103-1120) — the renderer's inbound bridge (`window.billdog.receive`)
|
|
175
|
+
* routes `state_set` into `GlobalStateStore`, which `useLocalization`
|
|
176
|
+
* subscribes to. Tolerates both bridge globals (`window.billdog` and the
|
|
177
|
+
* older `window.paywallBridge`).
|
|
178
|
+
*/
|
|
179
|
+
const sendLocaleUpdate = useCallback((loc: string) => {
|
|
180
|
+
if (!webViewRef.current || !loc) return;
|
|
181
|
+
const envelope = JSON.stringify({
|
|
182
|
+
version: 1,
|
|
183
|
+
type: 'state_set',
|
|
184
|
+
payload: { key: 'config:locale', value: loc },
|
|
185
|
+
});
|
|
186
|
+
const js = `(function(){
|
|
187
|
+
try {
|
|
188
|
+
if (window.billdog && window.billdog.receive) {
|
|
189
|
+
window.billdog.receive(${JSON.stringify(envelope)});
|
|
190
|
+
} else if (window.paywallBridge && window.paywallBridge.setState) {
|
|
191
|
+
window.paywallBridge.setState('config:locale', ${JSON.stringify(loc)});
|
|
192
|
+
}
|
|
193
|
+
} catch (e) {}
|
|
194
|
+
})(); true;`;
|
|
195
|
+
webViewRef.current.injectJavaScript(js);
|
|
196
|
+
}, []);
|
|
197
|
+
|
|
198
|
+
const handleMessage = useCallback(
|
|
199
|
+
(event: WebViewMessageEvent) => {
|
|
200
|
+
let message: BilldogMessage;
|
|
201
|
+
try {
|
|
202
|
+
message = JSON.parse(event.nativeEvent.data) as BilldogMessage;
|
|
203
|
+
} catch (parseErr) {
|
|
204
|
+
const err = parseErr instanceof Error ? parseErr : new Error(String(parseErr));
|
|
205
|
+
onError?.(err);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Validate envelope. Older renderer builds (pre-protocol-unification)
|
|
210
|
+
// may post flat objects without the `protocol` field; tolerate both.
|
|
211
|
+
const type = message?.type ?? '';
|
|
212
|
+
const payload = (message?.payload ?? {}) as Record<string, unknown>;
|
|
213
|
+
|
|
214
|
+
switch (type) {
|
|
215
|
+
case 'ready':
|
|
216
|
+
setIsLoading(false);
|
|
217
|
+
// Runtime half of the locale bridge: push `config:locale` into
|
|
218
|
+
// the renderer's GlobalStateStore via a `state_set` envelope so
|
|
219
|
+
// the active locale is applied even if the renderer ignored the
|
|
220
|
+
// URL `locale` query param. Mirrors Android
|
|
221
|
+
// `WebViewPaywallActivity.sendLocaleUpdate` (kt:1103-1120).
|
|
222
|
+
sendLocaleUpdate(effectiveLocale);
|
|
223
|
+
onReady?.();
|
|
224
|
+
break;
|
|
225
|
+
|
|
226
|
+
case 'purchase': {
|
|
227
|
+
const productIdentifier =
|
|
228
|
+
(payload.productIdentifier as string | undefined) ??
|
|
229
|
+
(payload.product_id as string | undefined) ??
|
|
230
|
+
'';
|
|
231
|
+
if (productIdentifier) onPurchase?.(productIdentifier);
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
case 'restore':
|
|
236
|
+
onRestore?.();
|
|
237
|
+
break;
|
|
238
|
+
|
|
239
|
+
case 'close':
|
|
240
|
+
onDismiss?.();
|
|
241
|
+
break;
|
|
242
|
+
|
|
243
|
+
case 'open_url': {
|
|
244
|
+
const url = payload.url as string | undefined;
|
|
245
|
+
if (url) onOpenUrl?.(url);
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
case 'track': {
|
|
250
|
+
const eventName = payload.eventName as string | undefined;
|
|
251
|
+
const properties = payload.properties as Record<string, unknown> | undefined;
|
|
252
|
+
if (eventName) onTrackEvent?.(eventName, properties);
|
|
253
|
+
break;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
case 'error': {
|
|
257
|
+
const msg =
|
|
258
|
+
(payload.message as string | undefined) ??
|
|
259
|
+
(payload.error as string | undefined) ??
|
|
260
|
+
'Unknown renderer error';
|
|
261
|
+
const err = new Error(msg);
|
|
262
|
+
setErrorState(err);
|
|
263
|
+
onError?.(err);
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
case 'execute_action': {
|
|
268
|
+
const paywallId = payload.paywallId as string | undefined;
|
|
269
|
+
const componentId = payload.componentId as string | undefined;
|
|
270
|
+
const actionType = payload.actionType as string | undefined;
|
|
271
|
+
const idempotencyKey = payload.idempotencyKey as string | undefined;
|
|
272
|
+
|
|
273
|
+
if (componentId && actionType) {
|
|
274
|
+
const executePromise = onExecuteAction
|
|
275
|
+
? onExecuteAction(paywallId ?? null, componentId, actionType, idempotencyKey ?? null)
|
|
276
|
+
: BillDog.executeAction(paywallId ?? null, componentId, actionType, idempotencyKey ?? null);
|
|
277
|
+
|
|
278
|
+
executePromise
|
|
279
|
+
.then((result) => {
|
|
280
|
+
const resultObj = {
|
|
281
|
+
actionType,
|
|
282
|
+
componentId,
|
|
283
|
+
success: true,
|
|
284
|
+
data: result,
|
|
285
|
+
idempotencyKey: idempotencyKey ?? null,
|
|
286
|
+
};
|
|
287
|
+
if (webViewRef.current) {
|
|
288
|
+
const js = `window.dispatchEvent(new CustomEvent('billdog:execute_action_result', { detail: ${JSON.stringify(resultObj)} })); true;`;
|
|
289
|
+
webViewRef.current.injectJavaScript(js);
|
|
290
|
+
}
|
|
291
|
+
})
|
|
292
|
+
.catch((err: Error) => {
|
|
293
|
+
const resultObj = {
|
|
294
|
+
actionType,
|
|
295
|
+
componentId,
|
|
296
|
+
success: false,
|
|
297
|
+
error: err.message || 'Action execution failed',
|
|
298
|
+
idempotencyKey: idempotencyKey ?? null,
|
|
299
|
+
};
|
|
300
|
+
if (webViewRef.current) {
|
|
301
|
+
const js = `window.dispatchEvent(new CustomEvent('billdog:execute_action_result', { detail: ${JSON.stringify(resultObj)} })); true;`;
|
|
302
|
+
webViewRef.current.injectJavaScript(js);
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
case 'state_changed':
|
|
310
|
+
// No-op — exposed for state-driven flows; consumer can hook
|
|
311
|
+
// via a future onStateChange prop if needed.
|
|
312
|
+
break;
|
|
313
|
+
|
|
314
|
+
default:
|
|
315
|
+
// Unknown message types are logged for debugging but don't
|
|
316
|
+
// surface as errors — the protocol may add new types over time.
|
|
317
|
+
if (__DEV__) {
|
|
318
|
+
// eslint-disable-next-line no-console
|
|
319
|
+
console.log('[WebViewPaywall] unknown message type:', type, payload);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
[
|
|
324
|
+
effectiveLocale,
|
|
325
|
+
sendLocaleUpdate,
|
|
326
|
+
onReady,
|
|
327
|
+
onPurchase,
|
|
328
|
+
onRestore,
|
|
329
|
+
onDismiss,
|
|
330
|
+
onOpenUrl,
|
|
331
|
+
onTrackEvent,
|
|
332
|
+
onError,
|
|
333
|
+
onExecuteAction,
|
|
334
|
+
],
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Public method for the consumer to push updated state into the
|
|
339
|
+
* renderer (e.g. after entitlement change). Mirrors the
|
|
340
|
+
* `paywallBridge.setState(...)` global exposed by the renderer in
|
|
341
|
+
* native-SDK mode.
|
|
342
|
+
*/
|
|
343
|
+
const injectState = useCallback((key: string, value: unknown) => {
|
|
344
|
+
if (!webViewRef.current) return;
|
|
345
|
+
const js = `window.paywallBridge && window.paywallBridge.setState && window.paywallBridge.setState(${JSON.stringify(key)}, ${JSON.stringify(value)}); true;`;
|
|
346
|
+
webViewRef.current.injectJavaScript(js);
|
|
347
|
+
}, []);
|
|
348
|
+
|
|
349
|
+
// Surface the method via a forwardRef-style imperative handle could
|
|
350
|
+
// be added; for the MVP we keep the API prop-only.
|
|
351
|
+
void injectState;
|
|
352
|
+
|
|
353
|
+
const WebViewComponent = loadWebView();
|
|
354
|
+
|
|
355
|
+
const content = (
|
|
356
|
+
<View style={styles.container}>
|
|
357
|
+
{isLoading && !errorState && (
|
|
358
|
+
<View style={styles.loadingContainer}>
|
|
359
|
+
<ActivityIndicator size="large" />
|
|
360
|
+
</View>
|
|
361
|
+
)}
|
|
362
|
+
{errorState && (
|
|
363
|
+
<View style={styles.errorContainer}>
|
|
364
|
+
<Text style={styles.errorText}>Failed to load paywall</Text>
|
|
365
|
+
<Text style={styles.errorDetails}>{errorState.message}</Text>
|
|
366
|
+
</View>
|
|
367
|
+
)}
|
|
368
|
+
{!errorState && !WebViewComponent && (
|
|
369
|
+
<View style={styles.errorContainer}>
|
|
370
|
+
<Text style={styles.errorText}>WebView unavailable</Text>
|
|
371
|
+
<Text style={styles.errorDetails}>
|
|
372
|
+
react-native-webview is not linked. Use the native paywall path or add the peer dependency.
|
|
373
|
+
</Text>
|
|
374
|
+
</View>
|
|
375
|
+
)}
|
|
376
|
+
{!errorState && WebViewComponent && (
|
|
377
|
+
<WebViewComponent
|
|
378
|
+
ref={webViewRef}
|
|
379
|
+
source={{ uri: rendererUrl }}
|
|
380
|
+
onMessage={handleMessage}
|
|
381
|
+
onError={(syntheticEvent: any) => {
|
|
382
|
+
const { nativeEvent } = syntheticEvent;
|
|
383
|
+
const err = new Error(nativeEvent.description ?? 'WebView load error');
|
|
384
|
+
setErrorState(err);
|
|
385
|
+
onError?.(err);
|
|
386
|
+
}}
|
|
387
|
+
onHttpError={(syntheticEvent: any) => {
|
|
388
|
+
const { nativeEvent } = syntheticEvent;
|
|
389
|
+
const err = new Error(`WebView HTTP ${nativeEvent.statusCode}`);
|
|
390
|
+
setErrorState(err);
|
|
391
|
+
onError?.(err);
|
|
392
|
+
}}
|
|
393
|
+
style={styles.webView}
|
|
394
|
+
javaScriptEnabled
|
|
395
|
+
domStorageEnabled
|
|
396
|
+
scrollEnabled
|
|
397
|
+
showsVerticalScrollIndicator={false}
|
|
398
|
+
// The renderer manages its own viewport; don't intercept taps.
|
|
399
|
+
originWhitelist={['*']}
|
|
400
|
+
/>
|
|
401
|
+
)}
|
|
402
|
+
</View>
|
|
403
|
+
);
|
|
404
|
+
|
|
405
|
+
if (modal) {
|
|
406
|
+
return (
|
|
407
|
+
<Modal visible={visible} animationType="slide" onRequestClose={() => onDismiss?.()}>
|
|
408
|
+
{content}
|
|
409
|
+
</Modal>
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
return content;
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
const styles = StyleSheet.create({
|
|
416
|
+
container: {
|
|
417
|
+
flex: 1,
|
|
418
|
+
backgroundColor: '#fff',
|
|
419
|
+
},
|
|
420
|
+
webView: {
|
|
421
|
+
flex: 1,
|
|
422
|
+
backgroundColor: 'transparent',
|
|
423
|
+
},
|
|
424
|
+
loadingContainer: {
|
|
425
|
+
position: 'absolute',
|
|
426
|
+
top: 0,
|
|
427
|
+
left: 0,
|
|
428
|
+
right: 0,
|
|
429
|
+
bottom: 0,
|
|
430
|
+
justifyContent: 'center',
|
|
431
|
+
alignItems: 'center',
|
|
432
|
+
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
|
433
|
+
zIndex: 1,
|
|
434
|
+
},
|
|
435
|
+
errorContainer: {
|
|
436
|
+
flex: 1,
|
|
437
|
+
justifyContent: 'center',
|
|
438
|
+
alignItems: 'center',
|
|
439
|
+
padding: 24,
|
|
440
|
+
},
|
|
441
|
+
errorText: {
|
|
442
|
+
fontSize: 18,
|
|
443
|
+
fontWeight: 'bold',
|
|
444
|
+
marginBottom: 8,
|
|
445
|
+
color: '#333',
|
|
446
|
+
},
|
|
447
|
+
errorDetails: {
|
|
448
|
+
fontSize: 13,
|
|
449
|
+
color: '#666',
|
|
450
|
+
textAlign: 'center',
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
export default WebViewPaywall;
|