expo-helium 0.8.5 → 3.0.5
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/build/HeliumPaywallSdk.types.d.ts +95 -5
- package/build/HeliumPaywallSdk.types.d.ts.map +1 -1
- package/build/HeliumPaywallSdk.types.js.map +1 -1
- package/build/HeliumPaywallSdkModule.d.ts +2 -1
- package/build/HeliumPaywallSdkModule.d.ts.map +1 -1
- package/build/HeliumPaywallSdkModule.js.map +1 -1
- package/build/index.d.ts +3 -6
- package/build/index.d.ts.map +1 -1
- package/build/index.js +138 -25
- package/build/index.js.map +1 -1
- package/build/revenuecat/revenuecat.d.ts.map +1 -1
- package/build/revenuecat/revenuecat.js +6 -4
- package/build/revenuecat/revenuecat.js.map +1 -1
- package/ios/HeliumPaywallSdk.podspec +1 -1
- package/ios/HeliumPaywallSdkModule.swift +129 -57
- package/package.json +1 -1
- package/src/HeliumPaywallSdk.types.ts +110 -6
- package/src/HeliumPaywallSdkModule.ts +6 -1
- package/src/index.ts +144 -35
- package/src/revenuecat/revenuecat.ts +7 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"revenuecat.js","sourceRoot":"","sources":["../../src/revenuecat/revenuecat.ts"],"names":[],"mappings":"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","sourcesContent":["import Purchases, {PURCHASES_ERROR_CODE, PurchasesStoreProduct} from 'react-native-purchases';\nimport type { PurchasesError, PurchasesPackage, CustomerInfoUpdateListener, CustomerInfo, PurchasesEntitlementInfo } from 'react-native-purchases';\nimport {HeliumPurchaseConfig, HeliumPurchaseResult} from \"../HeliumPaywallSdk.types\";\n\n// Rename the factory function\nexport function createRevenueCatPurchaseConfig(config?: {\n apiKey?: string;\n}): HeliumPurchaseConfig {\n const rcHandler = new RevenueCatHeliumHandler(config?.apiKey);\n return {\n apiKey: config?.apiKey,\n makePurchase: rcHandler.makePurchase.bind(rcHandler),\n restorePurchases: rcHandler.restorePurchases.bind(rcHandler),\n };\n}\n\nexport class RevenueCatHeliumHandler {\n private productIdToPackageMapping: Record<string, PurchasesPackage> = {};\n private isMappingInitialized: boolean = false;\n private initializationPromise: Promise<void> | null = null;\n\n private rcProductToPackageMapping: Record<string, PurchasesStoreProduct> = {};\n\n constructor(apiKey?: string) {\n if (apiKey) {\n Purchases.configure({ apiKey });\n } else {\n }\n this.initializePackageMapping();\n }\n\n private async initializePackageMapping(): Promise<void> {\n if (this.initializationPromise) {\n return this.initializationPromise;\n }\n this.initializationPromise = (async () => {\n try {\n const offerings = await Purchases.getOfferings();\n const allOfferings = offerings.all;\n for (const offering of Object.values(allOfferings)) {\n offering.availablePackages.forEach((pkg: PurchasesPackage) => {\n if (pkg.product?.identifier) {\n this.productIdToPackageMapping[pkg.product.identifier] = pkg;\n }\n });\n }\n this.isMappingInitialized = true;\n } catch (error) {\n this.isMappingInitialized = false;\n } finally {\n this.initializationPromise = null;\n }\n })();\n return this.initializationPromise;\n }\n\n private async ensureMappingInitialized(): Promise<void> {\n if (!this.isMappingInitialized && !this.initializationPromise) {\n await this.initializePackageMapping();\n } else if (this.initializationPromise) {\n await this.initializationPromise;\n }\n }\n\n async makePurchase(productId: string): Promise<HeliumPurchaseResult> {\n await this.ensureMappingInitialized();\n\n const pkg: PurchasesPackage | undefined = this.productIdToPackageMapping[productId];\n let rcProduct: PurchasesStoreProduct | undefined;\n if (!pkg) {\n // Use cached if available\n rcProduct = this.rcProductToPackageMapping[productId];\n if (!rcProduct) {\n // Try to retrieve now\n try {\n const rcProducts = await Purchases.getProducts([productId]);\n rcProduct = rcProducts.length > 0 ? rcProducts[0] : undefined;\n } catch {\n // 'failed' status will be returned\n }\n if (rcProduct) {\n this.rcProductToPackageMapping[productId] = rcProduct;\n }\n }\n }\n\n try {\n let customerInfo: CustomerInfo;\n if (pkg) {\n customerInfo = (await Purchases.purchasePackage(pkg)).customerInfo;\n } else if (rcProduct) {\n customerInfo = (await Purchases.purchaseStoreProduct(rcProduct)).customerInfo;\n } else {\n return { status: 'failed', error: `RevenueCat Product/Package not found for ID: ${productId}` };\n }\n const isActive = this.isProductActive(customerInfo, productId);\n if (isActive) {\n return { status: 'purchased' };\n } else {\n // This case might occur if the purchase succeeded but the entitlement wasn't immediately active\n // or if a different product became active.\n // Consider if polling/listening might be needed here too, similar to pending.\n // For now, returning failed as the specific product isn't confirmed active.\n return { status: 'failed', error: 'Purchase possibly complete but entitlement/subscription not active for this product.' };\n }\n } catch (error) {\n const purchasesError = error as PurchasesError;\n\n if (purchasesError?.code === PURCHASES_ERROR_CODE.PAYMENT_PENDING_ERROR) {\n // Wait for a terminal state for up to 5 seconds\n return new Promise((resolve) => {\n // Define the listener function separately to remove it later\n const updateListener: CustomerInfoUpdateListener = (updatedCustomerInfo: CustomerInfo) => {\n const isActive = this.isProductActive(updatedCustomerInfo, productId);\n if (isActive) {\n clearTimeout(timeoutId);\n // Remove listener using the function reference\n Purchases.removeCustomerInfoUpdateListener(updateListener);\n resolve({ status: 'purchased' });\n }\n };\n\n const timeoutId = setTimeout(() => {\n // Remove listener using the function reference on timeout\n Purchases.removeCustomerInfoUpdateListener(updateListener);\n resolve({ status: 'pending' });\n }, 5000);\n\n // Add the listener\n Purchases.addCustomerInfoUpdateListener(updateListener);\n });\n }\n\n if (purchasesError?.code === PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR) {\n return { status: 'cancelled' };\n }\n\n // Handle other errors\n return { status: 'failed', error: purchasesError?.message || 'RevenueCat purchase failed.' };\n }\n }\n\n // Helper function to check if a product is active in CustomerInfo\n private isProductActive(customerInfo: CustomerInfo, productId: string): boolean {\n return Object.values(customerInfo.entitlements.active).some((entitlement: PurchasesEntitlementInfo) => entitlement.productIdentifier === productId)\n || customerInfo.activeSubscriptions.includes(productId)\n || customerInfo.allPurchasedProductIdentifiers.includes(productId);\n }\n\n async restorePurchases(): Promise<boolean> {\n try {\n const customerInfo = await Purchases.restorePurchases();\n const isActive = Object.keys(customerInfo.entitlements.active).length > 0;\n return isActive;\n } catch (error) {\n return false;\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"revenuecat.js","sourceRoot":"","sources":["../../src/revenuecat/revenuecat.ts"],"names":[],"mappings":"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","sourcesContent":["import Purchases, {PURCHASES_ERROR_CODE, PurchasesStoreProduct} from 'react-native-purchases';\nimport type { PurchasesError, PurchasesPackage, CustomerInfoUpdateListener, CustomerInfo, PurchasesEntitlementInfo } from 'react-native-purchases';\nimport {HeliumPurchaseConfig, HeliumPurchaseResult} from \"../HeliumPaywallSdk.types\";\nimport {setRevenueCatAppUserId} from \"../index\";\n\n// Rename the factory function\nexport function createRevenueCatPurchaseConfig(config?: {\n apiKey?: string;\n}): HeliumPurchaseConfig {\n const rcHandler = new RevenueCatHeliumHandler(config?.apiKey);\n return {\n makePurchase: rcHandler.makePurchase.bind(rcHandler),\n restorePurchases: rcHandler.restorePurchases.bind(rcHandler),\n };\n}\n\nexport class RevenueCatHeliumHandler {\n private productIdToPackageMapping: Record<string, PurchasesPackage> = {};\n private isMappingInitialized: boolean = false;\n private initializationPromise: Promise<void> | null = null;\n\n private rcProductToPackageMapping: Record<string, PurchasesStoreProduct> = {};\n\n constructor(apiKey?: string) {\n if (apiKey) {\n Purchases.configure({ apiKey });\n }\n void this.initializePackageMapping();\n }\n\n private async initializePackageMapping(): Promise<void> {\n if (this.initializationPromise) {\n return this.initializationPromise;\n }\n this.initializationPromise = (async () => {\n try {\n // Keep this value as up-to-date as possible\n setRevenueCatAppUserId(await Purchases.getAppUserID());\n\n const offerings = await Purchases.getOfferings();\n const allOfferings = offerings.all;\n for (const offering of Object.values(allOfferings)) {\n offering.availablePackages.forEach((pkg: PurchasesPackage) => {\n if (pkg.product?.identifier) {\n this.productIdToPackageMapping[pkg.product.identifier] = pkg;\n }\n });\n }\n this.isMappingInitialized = true;\n } catch (error) {\n this.isMappingInitialized = false;\n } finally {\n this.initializationPromise = null;\n }\n })();\n return this.initializationPromise;\n }\n\n private async ensureMappingInitialized(): Promise<void> {\n if (!this.isMappingInitialized && !this.initializationPromise) {\n await this.initializePackageMapping();\n } else if (this.initializationPromise) {\n await this.initializationPromise;\n }\n }\n\n async makePurchase(productId: string): Promise<HeliumPurchaseResult> {\n await this.ensureMappingInitialized();\n // Keep this value as up-to-date as possible\n setRevenueCatAppUserId(await Purchases.getAppUserID());\n\n const pkg: PurchasesPackage | undefined = this.productIdToPackageMapping[productId];\n let rcProduct: PurchasesStoreProduct | undefined;\n if (!pkg) {\n // Use cached if available\n rcProduct = this.rcProductToPackageMapping[productId];\n if (!rcProduct) {\n // Try to retrieve now\n try {\n const rcProducts = await Purchases.getProducts([productId]);\n rcProduct = rcProducts.length > 0 ? rcProducts[0] : undefined;\n } catch {\n // 'failed' status will be returned\n }\n if (rcProduct) {\n this.rcProductToPackageMapping[productId] = rcProduct;\n }\n }\n }\n\n try {\n let customerInfo: CustomerInfo;\n if (pkg) {\n customerInfo = (await Purchases.purchasePackage(pkg)).customerInfo;\n } else if (rcProduct) {\n customerInfo = (await Purchases.purchaseStoreProduct(rcProduct)).customerInfo;\n } else {\n return { status: 'failed', error: `RevenueCat Product/Package not found for ID: ${productId}` };\n }\n const isActive = this.isProductActive(customerInfo, productId);\n if (isActive) {\n return { status: 'purchased' };\n } else {\n // This case might occur if the purchase succeeded but the entitlement wasn't immediately active\n // or if a different product became active.\n // Consider if polling/listening might be needed here too, similar to pending.\n // For now, returning failed as the specific product isn't confirmed active.\n return { status: 'failed', error: 'Purchase possibly complete but entitlement/subscription not active for this product.' };\n }\n } catch (error) {\n const purchasesError = error as PurchasesError;\n\n if (purchasesError?.code === PURCHASES_ERROR_CODE.PAYMENT_PENDING_ERROR) {\n // Wait for a terminal state for up to 5 seconds\n return new Promise((resolve) => {\n // Define the listener function separately to remove it later\n const updateListener: CustomerInfoUpdateListener = (updatedCustomerInfo: CustomerInfo) => {\n const isActive = this.isProductActive(updatedCustomerInfo, productId);\n if (isActive) {\n clearTimeout(timeoutId);\n // Remove listener using the function reference\n Purchases.removeCustomerInfoUpdateListener(updateListener);\n resolve({ status: 'purchased' });\n }\n };\n\n const timeoutId = setTimeout(() => {\n // Remove listener using the function reference on timeout\n Purchases.removeCustomerInfoUpdateListener(updateListener);\n resolve({ status: 'pending' });\n }, 5000);\n\n // Add the listener\n Purchases.addCustomerInfoUpdateListener(updateListener);\n });\n }\n\n if (purchasesError?.code === PURCHASES_ERROR_CODE.PURCHASE_CANCELLED_ERROR) {\n return { status: 'cancelled' };\n }\n\n // Handle other errors\n return { status: 'failed', error: purchasesError?.message || 'RevenueCat purchase failed.' };\n }\n }\n\n // Helper function to check if a product is active in CustomerInfo\n private isProductActive(customerInfo: CustomerInfo, productId: string): boolean {\n return Object.values(customerInfo.entitlements.active).some((entitlement: PurchasesEntitlementInfo) => entitlement.productIdentifier === productId)\n || customerInfo.activeSubscriptions.includes(productId)\n || customerInfo.allPurchasedProductIdentifiers.includes(productId);\n }\n\n async restorePurchases(): Promise<boolean> {\n try {\n const customerInfo = await Purchases.restorePurchases();\n const isActive = Object.keys(customerInfo.entitlements.active).length > 0;\n return isActive;\n } catch (error) {\n return false;\n }\n }\n}\n"]}
|
|
@@ -56,19 +56,53 @@ public class HeliumPaywallSdkModule: Module {
|
|
|
56
56
|
// ])
|
|
57
57
|
|
|
58
58
|
// Defines event names that the module can send to JavaScript.
|
|
59
|
-
Events("onHeliumPaywallEvent", "onDelegateActionEvent")
|
|
59
|
+
Events("onHeliumPaywallEvent", "onDelegateActionEvent", "paywallEventHandlers")
|
|
60
60
|
|
|
61
61
|
// todo use Record here? https://docs.expo.dev/modules/module-api/#records
|
|
62
62
|
Function("initialize") { (config: [String : Any]) in
|
|
63
|
-
let userTraitsMap = config["customUserTraits"] as? [String : Any]
|
|
63
|
+
let userTraitsMap = convertMarkersToBooleans(config["customUserTraits"] as? [String : Any])
|
|
64
64
|
let fallbackBundleURLString = config["fallbackBundleUrlString"] as? String
|
|
65
65
|
let fallbackBundleString = config["fallbackBundleString"] as? String
|
|
66
|
+
|
|
67
|
+
let paywallLoadingConfig = convertMarkersToBooleans(config["paywallLoadingConfig"] as? [String: Any])
|
|
68
|
+
let useLoadingState = paywallLoadingConfig?["useLoadingState"] as? Bool ?? true
|
|
69
|
+
let loadingBudget = paywallLoadingConfig?["loadingBudget"] as? TimeInterval ?? 2.0
|
|
70
|
+
|
|
71
|
+
var perTriggerLoadingConfig: [String: TriggerLoadingConfig]? = nil
|
|
72
|
+
if let perTriggerDict = paywallLoadingConfig?["perTriggerLoadingConfig"] as? [String: [String: Any]] {
|
|
73
|
+
var triggerConfigs: [String: TriggerLoadingConfig] = [:]
|
|
74
|
+
for (trigger, config) in perTriggerDict {
|
|
75
|
+
triggerConfigs[trigger] = TriggerLoadingConfig(
|
|
76
|
+
useLoadingState: config["useLoadingState"] as? Bool,
|
|
77
|
+
loadingBudget: config["loadingBudget"] as? TimeInterval
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
perTriggerLoadingConfig = triggerConfigs
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let useDefaultDelegate = config["useDefaultDelegate"] as? Bool ?? false
|
|
84
|
+
|
|
85
|
+
let delegateEventHandler: (HeliumEvent) -> Void = { [weak self] event in
|
|
86
|
+
var eventDict = event.toDictionary()
|
|
87
|
+
// Add deprecated fields for backwards compatibility
|
|
88
|
+
if let paywallName = eventDict["paywallName"] {
|
|
89
|
+
eventDict["paywallTemplateName"] = paywallName
|
|
90
|
+
}
|
|
91
|
+
if let error = eventDict["error"] {
|
|
92
|
+
eventDict["errorDescription"] = error
|
|
93
|
+
}
|
|
94
|
+
if let productId = eventDict["productId"] {
|
|
95
|
+
eventDict["productKey"] = productId
|
|
96
|
+
}
|
|
97
|
+
if let buttonName = eventDict["buttonName"] {
|
|
98
|
+
eventDict["ctaName"] = buttonName
|
|
99
|
+
}
|
|
100
|
+
self?.sendEvent("onHeliumPaywallEvent", eventDict)
|
|
101
|
+
}
|
|
66
102
|
|
|
67
103
|
// Create delegate with closures that send events to JavaScript
|
|
68
|
-
let
|
|
69
|
-
eventHandler:
|
|
70
|
-
self?.sendEvent("onHeliumPaywallEvent", event.toDictionary())
|
|
71
|
-
},
|
|
104
|
+
let internalDelegate = InternalDelegate(
|
|
105
|
+
eventHandler: delegateEventHandler,
|
|
72
106
|
purchaseHandler: { [weak self] productId in
|
|
73
107
|
guard let self else { return .failed(PurchaseError.purchaseFailed(errorMsg: "Module not active!")) }
|
|
74
108
|
// Check if there's already a purchase in progress and cancel it
|
|
@@ -110,6 +144,8 @@ public class HeliumPaywallSdkModule: Module {
|
|
|
110
144
|
}
|
|
111
145
|
)
|
|
112
146
|
|
|
147
|
+
let defaultDelegate = DefaultPurchaseDelegate(eventHandler: delegateEventHandler)
|
|
148
|
+
|
|
113
149
|
// Handle fallback bundle - either as URL string or JSON string
|
|
114
150
|
var fallbackBundleURL: URL? = nil
|
|
115
151
|
if let urlString = fallbackBundleURLString {
|
|
@@ -127,13 +163,17 @@ public class HeliumPaywallSdkModule: Module {
|
|
|
127
163
|
|
|
128
164
|
Helium.shared.initialize(
|
|
129
165
|
apiKey: config["apiKey"] as? String ?? "",
|
|
130
|
-
heliumPaywallDelegate:
|
|
131
|
-
|
|
166
|
+
heliumPaywallDelegate: useDefaultDelegate ? defaultDelegate : internalDelegate,
|
|
167
|
+
fallbackConfig: HeliumFallbackConfig.withMultipleFallbacks(
|
|
168
|
+
fallbackBundle: fallbackBundleURL,
|
|
169
|
+
useLoadingState: useLoadingState,
|
|
170
|
+
loadingBudget: loadingBudget,
|
|
171
|
+
perTriggerLoadingConfig: perTriggerLoadingConfig
|
|
172
|
+
),
|
|
132
173
|
customUserId: config["customUserId"] as? String,
|
|
133
174
|
customAPIEndpoint: config["customAPIEndpoint"] as? String,
|
|
134
175
|
customUserTraits: userTraitsMap != nil ? HeliumUserTraits(userTraitsMap!) : nil,
|
|
135
|
-
revenueCatAppUserId: config["revenueCatAppUserId"] as? String
|
|
136
|
-
fallbackBundleURL: fallbackBundleURL
|
|
176
|
+
revenueCatAppUserId: config["revenueCatAppUserId"] as? String
|
|
137
177
|
)
|
|
138
178
|
}
|
|
139
179
|
|
|
@@ -174,8 +214,25 @@ public class HeliumPaywallSdkModule: Module {
|
|
|
174
214
|
continuation.resume(returning: success)
|
|
175
215
|
}
|
|
176
216
|
|
|
177
|
-
Function("presentUpsell") { (trigger: String) in
|
|
178
|
-
|
|
217
|
+
Function("presentUpsell") { (trigger: String, customPaywallTraits: [String: Any]?) in
|
|
218
|
+
Helium.shared.presentUpsell(
|
|
219
|
+
trigger: trigger,
|
|
220
|
+
eventHandlers: PaywallEventHandlers.withHandlers(
|
|
221
|
+
onOpen: { [weak self] event in
|
|
222
|
+
self?.sendEvent("paywallEventHandlers", event.toDictionary())
|
|
223
|
+
},
|
|
224
|
+
onClose: { [weak self] event in
|
|
225
|
+
self?.sendEvent("paywallEventHandlers", event.toDictionary())
|
|
226
|
+
},
|
|
227
|
+
onDismissed: { [weak self] event in
|
|
228
|
+
self?.sendEvent("paywallEventHandlers", event.toDictionary())
|
|
229
|
+
},
|
|
230
|
+
onPurchaseSucceeded: { [weak self] event in
|
|
231
|
+
self?.sendEvent("paywallEventHandlers", event.toDictionary())
|
|
232
|
+
}
|
|
233
|
+
),
|
|
234
|
+
customPaywallTraits: convertMarkersToBooleans(customPaywallTraits)
|
|
235
|
+
)
|
|
179
236
|
}
|
|
180
237
|
|
|
181
238
|
Function("hideUpsell") {
|
|
@@ -221,10 +278,17 @@ public class HeliumPaywallSdkModule: Module {
|
|
|
221
278
|
let canPresent: Bool
|
|
222
279
|
let reason: String
|
|
223
280
|
|
|
281
|
+
let useLoading = Helium.shared.loadingStateEnabledFor(trigger: trigger)
|
|
282
|
+
let downloadInProgress = Helium.shared.getDownloadStatus() == .inProgress
|
|
283
|
+
|
|
224
284
|
if paywallsLoaded && hasTrigger {
|
|
225
285
|
// Normal case - paywall is ready
|
|
226
286
|
canPresent = true
|
|
227
287
|
reason = "ready"
|
|
288
|
+
} else if downloadInProgress && useLoading {
|
|
289
|
+
// Loading case - paywall still downloading
|
|
290
|
+
canPresent = true
|
|
291
|
+
reason = "loading"
|
|
228
292
|
} else if HeliumFallbackViewManager.shared.getFallbackInfo(trigger: trigger) != nil {
|
|
229
293
|
// Fallback is available (via downloaded bundle)
|
|
230
294
|
canPresent = true
|
|
@@ -241,6 +305,10 @@ public class HeliumPaywallSdkModule: Module {
|
|
|
241
305
|
)
|
|
242
306
|
}
|
|
243
307
|
|
|
308
|
+
Function("setRevenueCatAppUserId") { (rcAppUserId: String) in
|
|
309
|
+
Helium.shared.setRevenueCatAppUserId(rcAppUserId)
|
|
310
|
+
}
|
|
311
|
+
|
|
244
312
|
Function("handleDeepLink") { (urlString: String) in
|
|
245
313
|
guard let url = URL(string: urlString) else {
|
|
246
314
|
return false
|
|
@@ -249,15 +317,6 @@ public class HeliumPaywallSdkModule: Module {
|
|
|
249
317
|
return Helium.shared.handleDeepLink(url)
|
|
250
318
|
}
|
|
251
319
|
|
|
252
|
-
// Defines a JavaScript function that always returns a Promise and whose native code
|
|
253
|
-
// is by default dispatched on the different thread than the JavaScript runtime runs on.
|
|
254
|
-
// AsyncFunction("setValueAsync") { (value: String) in
|
|
255
|
-
// // Send an event to JavaScript.
|
|
256
|
-
// self.sendEvent("onHeliumPaywallEvent", [
|
|
257
|
-
// "value": value
|
|
258
|
-
// ])
|
|
259
|
-
// }
|
|
260
|
-
|
|
261
320
|
// Enables the module to be used as a native view. Definition components that are accepted as part of the
|
|
262
321
|
// view definition: Prop, Events.
|
|
263
322
|
View(HeliumPaywallSdkView.self) {
|
|
@@ -271,15 +330,51 @@ public class HeliumPaywallSdkModule: Module {
|
|
|
271
330
|
Events("onLoad")
|
|
272
331
|
}
|
|
273
332
|
}
|
|
333
|
+
|
|
334
|
+
/// Recursively converts special marker strings back to boolean values to restore
|
|
335
|
+
/// type information that was preserved when passing through native bridge
|
|
336
|
+
///
|
|
337
|
+
/// Native bridge converts booleans to NSNumber (0/1), so we use
|
|
338
|
+
/// special marker strings to preserve the original intent. This helper converts:
|
|
339
|
+
/// - "__helium_rn_bool_true__" -> true
|
|
340
|
+
/// - "__helium_rn_bool_false__" -> false
|
|
341
|
+
/// - All other values remain unchanged
|
|
342
|
+
private func convertMarkersToBooleans(_ input: [String: Any]?) -> [String: Any]? {
|
|
343
|
+
guard let input = input else { return nil }
|
|
344
|
+
|
|
345
|
+
var result: [String: Any] = [:]
|
|
346
|
+
for (key, value) in input {
|
|
347
|
+
result[key] = convertValueMarkersToBooleans(value)
|
|
348
|
+
}
|
|
349
|
+
return result
|
|
350
|
+
}
|
|
351
|
+
/// Helper to recursively convert marker strings in any value type
|
|
352
|
+
private func convertValueMarkersToBooleans(_ value: Any) -> Any {
|
|
353
|
+
if let stringValue = value as? String {
|
|
354
|
+
switch stringValue {
|
|
355
|
+
case "__helium_rn_bool_true__":
|
|
356
|
+
return true
|
|
357
|
+
case "__helium_rn_bool_false__":
|
|
358
|
+
return false
|
|
359
|
+
default:
|
|
360
|
+
return stringValue
|
|
361
|
+
}
|
|
362
|
+
} else if let dictValue = value as? [String: Any] {
|
|
363
|
+
return convertMarkersToBooleans(dictValue) ?? [:]
|
|
364
|
+
} else if let arrayValue = value as? [Any] {
|
|
365
|
+
return arrayValue.map { convertValueMarkersToBooleans($0) }
|
|
366
|
+
}
|
|
367
|
+
return value
|
|
368
|
+
}
|
|
274
369
|
}
|
|
275
370
|
|
|
276
371
|
fileprivate class InternalDelegate: HeliumPaywallDelegate {
|
|
277
|
-
private let eventHandler: (
|
|
372
|
+
private let eventHandler: (HeliumEvent) -> Void
|
|
278
373
|
private let purchaseHandler: (String) async -> HeliumPaywallTransactionStatus
|
|
279
374
|
private let restoreHandler: () async -> Bool
|
|
280
375
|
|
|
281
376
|
init(
|
|
282
|
-
eventHandler: @escaping (
|
|
377
|
+
eventHandler: @escaping (HeliumEvent) -> Void,
|
|
283
378
|
purchaseHandler: @escaping (String) async -> HeliumPaywallTransactionStatus,
|
|
284
379
|
restoreHandler: @escaping () async -> Bool
|
|
285
380
|
) {
|
|
@@ -296,43 +391,20 @@ fileprivate class InternalDelegate: HeliumPaywallDelegate {
|
|
|
296
391
|
return await restoreHandler()
|
|
297
392
|
}
|
|
298
393
|
|
|
299
|
-
|
|
394
|
+
func onPaywallEvent(_ event: any HeliumEvent) {
|
|
300
395
|
eventHandler(event)
|
|
301
396
|
}
|
|
302
397
|
}
|
|
303
398
|
|
|
304
|
-
fileprivate
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
Text("Something went wrong loading the paywall. Make sure you used the right trigger.")
|
|
316
|
-
.font(.body)
|
|
317
|
-
.multilineTextAlignment(.center)
|
|
318
|
-
.foregroundColor(.secondary)
|
|
319
|
-
|
|
320
|
-
Spacer()
|
|
321
|
-
|
|
322
|
-
Button(action: {
|
|
323
|
-
presentationMode.wrappedValue.dismiss()
|
|
324
|
-
}) {
|
|
325
|
-
Text("Close")
|
|
326
|
-
.font(.headline)
|
|
327
|
-
.foregroundColor(.white)
|
|
328
|
-
.frame(maxWidth: .infinity)
|
|
329
|
-
.padding()
|
|
330
|
-
.background(Color.blue)
|
|
331
|
-
.cornerRadius(10)
|
|
332
|
-
}
|
|
333
|
-
.padding(.horizontal, 40)
|
|
334
|
-
.padding(.bottom, 40)
|
|
335
|
-
}
|
|
336
|
-
.padding()
|
|
399
|
+
fileprivate class DefaultPurchaseDelegate: StoreKitDelegate {
|
|
400
|
+
private let eventHandler: (HeliumEvent) -> Void
|
|
401
|
+
init(
|
|
402
|
+
eventHandler: @escaping (HeliumEvent) -> Void,
|
|
403
|
+
) {
|
|
404
|
+
self.eventHandler = eventHandler
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
override func onPaywallEvent(_ event: any HeliumEvent) {
|
|
408
|
+
eventHandler(event)
|
|
337
409
|
}
|
|
338
410
|
}
|
package/package.json
CHANGED
|
@@ -7,12 +7,30 @@ export type OnLoadEventPayload = {
|
|
|
7
7
|
export type HeliumPaywallSdkModuleEvents = {
|
|
8
8
|
onHeliumPaywallEvent: (params: HeliumPaywallEvent) => void;
|
|
9
9
|
onDelegateActionEvent: (params: DelegateActionEvent) => void;
|
|
10
|
+
paywallEventHandlers: (params: HeliumPaywallEvent) => void;
|
|
10
11
|
};
|
|
11
12
|
export type HeliumPaywallEvent = {
|
|
12
|
-
type:
|
|
13
|
+
type: 'paywallOpen' | 'paywallClose' | 'paywallDismissed' |
|
|
14
|
+
'paywallOpenFailed' | 'paywallSkipped' | 'paywallButtonPressed' |
|
|
15
|
+
'productSelected' | 'purchasePressed' | 'purchaseSucceeded' |
|
|
16
|
+
'purchaseCancelled' | 'purchaseFailed' | 'purchaseRestored' |
|
|
17
|
+
'purchaseRestoreFailed' | 'purchasePending' | 'initializeStart' |
|
|
18
|
+
'paywallsDownloadSuccess' | 'paywallsDownloadError' | 'paywallWebViewRendered';
|
|
13
19
|
triggerName?: string;
|
|
20
|
+
paywallName?: string;
|
|
21
|
+
/**
|
|
22
|
+
* @deprecated Use `paywallName` instead.
|
|
23
|
+
*/
|
|
14
24
|
paywallTemplateName?: string;
|
|
25
|
+
productId?: string;
|
|
26
|
+
/**
|
|
27
|
+
* @deprecated Use `productId` instead.
|
|
28
|
+
*/
|
|
15
29
|
productKey?: string;
|
|
30
|
+
buttonName?: string;
|
|
31
|
+
/**
|
|
32
|
+
* @deprecated Use `buttonName` instead.
|
|
33
|
+
*/
|
|
16
34
|
ctaName?: string;
|
|
17
35
|
configId?: string;
|
|
18
36
|
numAttempts?: number;
|
|
@@ -22,7 +40,16 @@ export type HeliumPaywallEvent = {
|
|
|
22
40
|
fontsDownloadTimeTakenMS?: number;
|
|
23
41
|
bundleDownloadTimeMS?: number;
|
|
24
42
|
dismissAll?: boolean;
|
|
43
|
+
isSecondTry?: boolean;
|
|
44
|
+
error?: string;
|
|
45
|
+
/**
|
|
46
|
+
* @deprecated Use `error` instead.
|
|
47
|
+
*/
|
|
25
48
|
errorDescription?: string;
|
|
49
|
+
/**
|
|
50
|
+
* Unix timestamp in seconds
|
|
51
|
+
*/
|
|
52
|
+
timestamp?: number;
|
|
26
53
|
};
|
|
27
54
|
export type DelegateActionEvent = {
|
|
28
55
|
type: 'purchase' | 'restore';
|
|
@@ -49,9 +76,6 @@ export type HeliumDownloadStatus = 'downloadSuccess' | 'downloadFailure' | 'inPr
|
|
|
49
76
|
export interface HeliumPurchaseConfig {
|
|
50
77
|
makePurchase: (productId: string) => Promise<HeliumPurchaseResult>;
|
|
51
78
|
restorePurchases: () => Promise<boolean>;
|
|
52
|
-
|
|
53
|
-
/** Optional RevenueCat API Key. If not provided, RevenueCat must be configured elsewhere. */
|
|
54
|
-
apiKey?: string;
|
|
55
79
|
}
|
|
56
80
|
|
|
57
81
|
// Helper function for creating Custom Purchase Config
|
|
@@ -65,17 +89,48 @@ export function createCustomPurchaseConfig(callbacks: {
|
|
|
65
89
|
};
|
|
66
90
|
}
|
|
67
91
|
|
|
92
|
+
export type TriggerLoadingConfig = {
|
|
93
|
+
/** Whether to show loading state for this trigger. Set to nil to use the global `useLoadingState` setting. */
|
|
94
|
+
useLoadingState?: boolean;
|
|
95
|
+
/** Maximum seconds to show loading for this trigger. Set to nil to use the global `loadingBudget` setting. */
|
|
96
|
+
loadingBudget?: number;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export type HeliumPaywallLoadingConfig = {
|
|
100
|
+
/**
|
|
101
|
+
* Whether to show a loading state while fetching paywall configuration.
|
|
102
|
+
* When true, shows a loading view for up to `loadingBudget` seconds before falling back.
|
|
103
|
+
* Default: true
|
|
104
|
+
*/
|
|
105
|
+
useLoadingState?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Maximum time (in seconds) to show the loading state before displaying fallback.
|
|
108
|
+
* After this timeout, the fallback view will be shown even if the paywall is still downloading.
|
|
109
|
+
* Default: 2.0 seconds
|
|
110
|
+
*/
|
|
111
|
+
loadingBudget?: number;
|
|
112
|
+
/**
|
|
113
|
+
* Optional per-trigger loading configuration overrides.
|
|
114
|
+
* Use this to customize loading behavior for specific triggers.
|
|
115
|
+
* Keys are trigger names, values are TriggerLoadingConfig instances.
|
|
116
|
+
* Example: Disable loading for "onboarding" trigger while keeping it for others.
|
|
117
|
+
*/
|
|
118
|
+
perTriggerLoadingConfig?: Record<string, TriggerLoadingConfig>;
|
|
119
|
+
};
|
|
120
|
+
|
|
68
121
|
export interface HeliumConfig {
|
|
69
122
|
/** Your Helium API Key */
|
|
70
123
|
apiKey: string;
|
|
71
124
|
/** Configuration for handling purchases. Can be custom functions or a pre-built handler config. */
|
|
72
|
-
purchaseConfig
|
|
125
|
+
purchaseConfig?: HeliumPurchaseConfig;
|
|
73
126
|
/** Callback for receiving all Helium paywall events. */
|
|
74
127
|
onHeliumPaywallEvent: (event: HeliumPaywallEvent) => void; // Still mandatory
|
|
75
128
|
|
|
76
129
|
// Optional configurations
|
|
130
|
+
/** Fallback bundle in the rare situation that paywall is not ready to be shown. Highly recommended. See docs at https://docs.tryhelium.com/guides/fallback-bundle#react-native */
|
|
77
131
|
fallbackBundle?: object;
|
|
78
|
-
|
|
132
|
+
/** Configure loading behavior for paywalls that are mid-download. */
|
|
133
|
+
paywallLoadingConfig?: HeliumPaywallLoadingConfig;
|
|
79
134
|
customUserId?: string;
|
|
80
135
|
customAPIEndpoint?: string;
|
|
81
136
|
customUserTraits?: Record<string, any>;
|
|
@@ -90,13 +145,62 @@ export interface NativeHeliumConfig {
|
|
|
90
145
|
revenueCatAppUserId?: string;
|
|
91
146
|
fallbackBundleUrlString?: string;
|
|
92
147
|
fallbackBundleString?: string;
|
|
148
|
+
paywallLoadingConfig?: HeliumPaywallLoadingConfig;
|
|
149
|
+
useDefaultDelegate?: boolean;
|
|
93
150
|
}
|
|
94
151
|
|
|
152
|
+
export type PresentUpsellParams = {
|
|
153
|
+
triggerName: string;
|
|
154
|
+
/** Optional. This will be called when paywall fails to show due to an unsuccessful paywall download or if an invalid trigger is provided. */
|
|
155
|
+
onFallback?: () => void;
|
|
156
|
+
eventHandlers?: PaywallEventHandlers;
|
|
157
|
+
customPaywallTraits?: Record<string, any>;
|
|
158
|
+
};
|
|
159
|
+
|
|
95
160
|
export interface PaywallInfo {
|
|
96
161
|
paywallTemplateName: string;
|
|
97
162
|
shouldShow: boolean;
|
|
98
163
|
}
|
|
99
164
|
|
|
165
|
+
// Event handler types for per-presentation event handling
|
|
166
|
+
export interface PaywallEventHandlers {
|
|
167
|
+
onOpen?: (event: PaywallOpenEvent) => void;
|
|
168
|
+
onClose?: (event: PaywallCloseEvent) => void;
|
|
169
|
+
onDismissed?: (event: PaywallDismissedEvent) => void;
|
|
170
|
+
onPurchaseSucceeded?: (event: PurchaseSucceededEvent) => void;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Typed event interfaces
|
|
174
|
+
export interface PaywallOpenEvent {
|
|
175
|
+
type: 'paywallOpen';
|
|
176
|
+
triggerName: string;
|
|
177
|
+
paywallName: string;
|
|
178
|
+
isSecondTry: boolean;
|
|
179
|
+
viewType?: 'presented' | 'embedded' | 'triggered';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export interface PaywallCloseEvent {
|
|
183
|
+
type: 'paywallClose';
|
|
184
|
+
triggerName: string;
|
|
185
|
+
paywallName: string;
|
|
186
|
+
isSecondTry: boolean;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export interface PaywallDismissedEvent {
|
|
190
|
+
type: 'paywallDismissed';
|
|
191
|
+
triggerName: string;
|
|
192
|
+
paywallName: string;
|
|
193
|
+
isSecondTry: boolean;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface PurchaseSucceededEvent {
|
|
197
|
+
type: 'purchaseSucceeded';
|
|
198
|
+
productId: string;
|
|
199
|
+
triggerName: string;
|
|
200
|
+
paywallName: string;
|
|
201
|
+
isSecondTry: boolean;
|
|
202
|
+
}
|
|
203
|
+
|
|
100
204
|
export const HELIUM_CTA_NAMES = {
|
|
101
205
|
SCHEDULE_CALL: 'schedule_call',
|
|
102
206
|
SUBSCRIBE_BUTTON: 'subscribe_button',
|
|
@@ -21,7 +21,10 @@ interface CanPresentUpsellResult {
|
|
|
21
21
|
declare class HeliumPaywallSdkModule extends NativeModule<HeliumPaywallSdkModuleEvents> {
|
|
22
22
|
initialize(config: NativeHeliumConfig): void;
|
|
23
23
|
|
|
24
|
-
presentUpsell(
|
|
24
|
+
presentUpsell(
|
|
25
|
+
triggerName: string,
|
|
26
|
+
customPaywallTraits?: Record<string, any>,
|
|
27
|
+
): void;
|
|
25
28
|
|
|
26
29
|
hideUpsell(): void;
|
|
27
30
|
|
|
@@ -47,6 +50,8 @@ declare class HeliumPaywallSdkModule extends NativeModule<HeliumPaywallSdkModule
|
|
|
47
50
|
getPaywallInfo(trigger: string): PaywallInfoResult;
|
|
48
51
|
|
|
49
52
|
handleDeepLink(urlString: string): boolean;
|
|
53
|
+
|
|
54
|
+
setRevenueCatAppUserId(rcAppUserId: string): void;
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
// This call loads the native module object from the JSI.
|