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.
@@ -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"]}
@@ -19,7 +19,7 @@ Pod::Spec.new do |s|
19
19
  s.static_framework = true
20
20
 
21
21
  s.dependency 'ExpoModulesCore'
22
- s.dependency 'Helium', '2.3.0'
22
+ s.dependency 'Helium', '3.0.5'
23
23
 
24
24
  # Swift/Objective-C compatibility
25
25
  s.pod_target_xcconfig = {
@@ -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 delegate = InternalDelegate(
69
- eventHandler: { [weak self] event in
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: delegate,
131
- fallbackPaywall: FallbackView(),
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
- Helium.shared.presentUpsell(trigger: trigger)
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: (HeliumPaywallEvent) -> Void
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 (HeliumPaywallEvent) -> Void,
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
- public func onHeliumPaywallEvent(event: HeliumPaywallEvent) {
394
+ func onPaywallEvent(_ event: any HeliumEvent) {
300
395
  eventHandler(event)
301
396
  }
302
397
  }
303
398
 
304
- fileprivate struct FallbackView: View {
305
- @Environment(\.presentationMode) var presentationMode
306
-
307
- var body: some View {
308
- VStack(spacing: 20) {
309
- Spacer()
310
-
311
- Text("Fallback Paywall")
312
- .font(.title)
313
- .fontWeight(.bold)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-helium",
3
- "version": "0.8.5",
3
+ "version": "3.0.5",
4
4
  "description": "Helium paywalls expo sdk",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -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: string;
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: HeliumPurchaseConfig;
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
- triggers?: string[];
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(triggerName: string): void;
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.