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/src/index.ts CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  DelegateActionEvent,
3
3
  HeliumConfig,
4
4
  HeliumPaywallEvent,
5
- NativeHeliumConfig, PaywallInfo,
5
+ NativeHeliumConfig, PaywallEventHandlers, PaywallInfo, PresentUpsellParams,
6
6
  } from "./HeliumPaywallSdk.types";
7
7
  import HeliumPaywallSdkModule from "./HeliumPaywallSdkModule";
8
8
  import { EventSubscription } from 'expo-modules-core';
@@ -20,6 +20,10 @@ function addDelegateActionEventListener(listener: (event: DelegateActionEvent) =
20
20
  return HeliumPaywallSdkModule.addListener('onDelegateActionEvent', listener);
21
21
  }
22
22
 
23
+ function addPaywallEventHandlersListener(listener: (event: HeliumPaywallEvent) => void): EventSubscription {
24
+ return HeliumPaywallSdkModule.addListener('paywallEventHandlers', listener);
25
+ }
26
+
23
27
  let isInitialized = false;
24
28
  export const initialize = (config: HeliumConfig) => {
25
29
  if (isInitialized) {
@@ -29,36 +33,44 @@ export const initialize = (config: HeliumConfig) => {
29
33
 
30
34
  HeliumPaywallSdkModule.removeAllListeners('onHeliumPaywallEvent');
31
35
  HeliumPaywallSdkModule.removeAllListeners('onDelegateActionEvent');
36
+ HeliumPaywallSdkModule.removeAllListeners('paywallEventHandlers');
32
37
 
33
38
  // Set up listener for paywall events
34
39
  addHeliumPaywallEventListener((event) => {
40
+ handlePaywallEvent(event);
35
41
  config.onHeliumPaywallEvent(event);
36
42
  });
37
43
 
38
44
  // Set up delegate action listener for purchase and restore operations
39
- addDelegateActionEventListener(async (event) => {
40
- try {
41
- if (event.type === 'purchase') {
42
- if (event.productId) {
43
- const result = await config.purchaseConfig.makePurchase(event.productId);
44
- HeliumPaywallSdkModule.handlePurchaseResult(result.status, result.error);
45
- } else {
46
- HeliumPaywallSdkModule.handlePurchaseResult('failed', 'No product ID for purchase event.');
45
+ const purchaseConfig = config.purchaseConfig;
46
+ if (purchaseConfig) {
47
+ addDelegateActionEventListener(async (event) => {
48
+ try {
49
+ if (event.type === 'purchase') {
50
+ if (event.productId) {
51
+ const result = await purchaseConfig.makePurchase(event.productId);
52
+ HeliumPaywallSdkModule.handlePurchaseResult(result.status, result.error);
53
+ } else {
54
+ HeliumPaywallSdkModule.handlePurchaseResult('failed', 'No product ID for purchase event.');
55
+ }
56
+ } else if (event.type === 'restore') {
57
+ const success = await purchaseConfig.restorePurchases();
58
+ HeliumPaywallSdkModule.handleRestoreResult(success);
59
+ }
60
+ } catch (error) {
61
+ // Send failure result based on action type
62
+ if (event.type === 'purchase') {
63
+ console.log('[Helium] Unexpected error: ', error);
64
+ HeliumPaywallSdkModule.handlePurchaseResult('failed');
65
+ } else if (event.type === 'restore') {
66
+ HeliumPaywallSdkModule.handleRestoreResult(false);
47
67
  }
48
68
  }
49
- else if (event.type === 'restore') {
50
- const success = await config.purchaseConfig.restorePurchases();
51
- HeliumPaywallSdkModule.handleRestoreResult(success);
52
- }
53
- } catch (error) {
54
- // Send failure result based on action type
55
- if (event.type === 'purchase') {
56
- console.log('[Helium] Unexpected error: ', error);
57
- HeliumPaywallSdkModule.handlePurchaseResult('failed');
58
- } else if (event.type === 'restore') {
59
- HeliumPaywallSdkModule.handleRestoreResult(false);
60
- }
61
- }
69
+ });
70
+ }
71
+
72
+ addPaywallEventHandlersListener((event) => {
73
+ callPaywallEventHandlers(event);
62
74
  });
63
75
 
64
76
  nativeInitializeAsync(config).catch(error => {
@@ -95,24 +107,27 @@ const nativeInitializeAsync = async (config: HeliumConfig) => {
95
107
  apiKey: config.apiKey,
96
108
  customUserId: config.customUserId,
97
109
  customAPIEndpoint: config.customAPIEndpoint,
98
- customUserTraits: config.customUserTraits,
110
+ customUserTraits: convertBooleansToMarkers(config.customUserTraits),
99
111
  revenueCatAppUserId: config.revenueCatAppUserId,
100
112
  fallbackBundleUrlString: fallbackBundleUrlString,
101
113
  fallbackBundleString: fallbackBundleString,
114
+ paywallLoadingConfig: convertBooleansToMarkers(config.paywallLoadingConfig),
115
+ useDefaultDelegate: !config.purchaseConfig,
102
116
  };
103
117
 
104
118
  // Initialize the native module
105
119
  HeliumPaywallSdkModule.initialize(nativeConfig);
106
120
  };
107
121
 
122
+ let paywallEventHandlers: PaywallEventHandlers | undefined;
123
+ let presentOnFallback: (() => void) | undefined;
108
124
  export const presentUpsell = ({
109
125
  triggerName,
110
- onFallback
111
- }: {
112
- triggerName: string;
113
- onFallback?: () => void;
114
- }) => {
115
- const { canPresent, reason } = HeliumPaywallSdkModule.canPresentUpsell(triggerName);
126
+ onFallback,
127
+ eventHandlers,
128
+ customPaywallTraits,
129
+ }: PresentUpsellParams) => {
130
+ const {canPresent, reason} = HeliumPaywallSdkModule.canPresentUpsell(triggerName);
116
131
 
117
132
  if (!canPresent) {
118
133
  console.log(
@@ -124,17 +139,83 @@ export const presentUpsell = ({
124
139
  }
125
140
 
126
141
  try {
127
- HeliumPaywallSdkModule.presentUpsell(triggerName);
142
+ paywallEventHandlers = eventHandlers;
143
+ presentOnFallback = onFallback;
144
+ HeliumPaywallSdkModule.presentUpsell(triggerName, convertBooleansToMarkers(customPaywallTraits));
128
145
  } catch (error) {
129
146
  console.log('Helium present error', error);
147
+ paywallEventHandlers = undefined;
148
+ presentOnFallback = undefined;
130
149
  onFallback?.();
131
150
  HeliumPaywallSdkModule.fallbackOpenOrCloseEvent(triggerName, true, 'presented');
132
151
  }
133
152
  };
134
153
 
154
+ function callPaywallEventHandlers(event: HeliumPaywallEvent) {
155
+ if (paywallEventHandlers) {
156
+ switch (event.type) {
157
+ case 'paywallOpen':
158
+ paywallEventHandlers?.onOpen?.({
159
+ type: 'paywallOpen',
160
+ triggerName: event.triggerName ?? 'unknown',
161
+ paywallName: event.paywallName ?? 'unknown',
162
+ isSecondTry: event.isSecondTry ?? false,
163
+ viewType: 'presented',
164
+ });
165
+ break;
166
+ case 'paywallClose':
167
+ paywallEventHandlers?.onClose?.({
168
+ type: 'paywallClose',
169
+ triggerName: event.triggerName ?? 'unknown',
170
+ paywallName: event.paywallName ?? 'unknown',
171
+ isSecondTry: event.isSecondTry ?? false,
172
+ });
173
+ break;
174
+ case 'paywallDismissed':
175
+ paywallEventHandlers?.onDismissed?.({
176
+ type: 'paywallDismissed',
177
+ triggerName: event.triggerName ?? 'unknown',
178
+ paywallName: event.paywallName ?? 'unknown',
179
+ isSecondTry: event.isSecondTry ?? false,
180
+ });
181
+ break;
182
+ case 'purchaseSucceeded':
183
+ paywallEventHandlers?.onPurchaseSucceeded?.({
184
+ type: 'purchaseSucceeded',
185
+ productId: event.productId ?? 'unknown',
186
+ triggerName: event.triggerName ?? 'unknown',
187
+ paywallName: event.paywallName ?? 'unknown',
188
+ isSecondTry: event.isSecondTry ?? false,
189
+ });
190
+ break;
191
+ }
192
+ }
193
+ }
194
+
195
+ function handlePaywallEvent(event: HeliumPaywallEvent) {
196
+ switch (event.type) {
197
+ case 'paywallClose':
198
+ if (!event.isSecondTry) {
199
+ paywallEventHandlers = undefined;
200
+ }
201
+ presentOnFallback = undefined;
202
+ break;
203
+ case 'paywallSkipped':
204
+ paywallEventHandlers = undefined;
205
+ presentOnFallback = undefined;
206
+ break;
207
+ case 'paywallOpenFailed':
208
+ paywallEventHandlers = undefined;
209
+ presentOnFallback?.();
210
+ presentOnFallback = undefined;
211
+ break;
212
+ }
213
+ }
214
+
135
215
  export const hideUpsell = HeliumPaywallSdkModule.hideUpsell;
136
216
  export const hideAllUpsells = HeliumPaywallSdkModule.hideAllUpsells;
137
217
  export const getDownloadStatus = HeliumPaywallSdkModule.getDownloadStatus;
218
+ export const setRevenueCatAppUserId = HeliumPaywallSdkModule.setRevenueCatAppUserId;
138
219
 
139
220
  export const getPaywallInfo = (trigger: string): PaywallInfo | undefined => {
140
221
  const result = HeliumPaywallSdkModule.getPaywallInfo(trigger);
@@ -161,9 +242,37 @@ export const handleDeepLink = (url: string | null) => {
161
242
  return false;
162
243
  };
163
244
 
164
- export {createCustomPurchaseConfig, HELIUM_CTA_NAMES} from './HeliumPaywallSdk.types';
245
+ /**
246
+ * Recursively converts boolean values to special marker strings to preserve
247
+ * type information when passing through native bridge.
248
+ *
249
+ * Native bridge converts booleans to NSNumber (0/1), making them
250
+ * indistinguishable from actual numeric values. This helper converts:
251
+ * - true -> "__helium_rn_bool_true__"
252
+ * - false -> "__helium_rn_bool_false__"
253
+ * - All other values remain unchanged
254
+ */
255
+ function convertBooleansToMarkers(input: Record<string, any> | undefined): Record<string, any> | undefined {
256
+ if (!input) return undefined;
165
257
 
166
- export type {
167
- HeliumTransactionStatus,
168
- HeliumConfig,
169
- } from './HeliumPaywallSdk.types';
258
+ const result: Record<string, any> = {};
259
+ for (const [key, value] of Object.entries(input)) {
260
+ result[key] = convertValueBooleansToMarkers(value);
261
+ }
262
+ return result;
263
+ }
264
+ /**
265
+ * Helper to recursively convert booleans in any value type
266
+ */
267
+ function convertValueBooleansToMarkers(value: any): any {
268
+ if (typeof value === 'boolean') {
269
+ return value ? "__helium_rn_bool_true__" : "__helium_rn_bool_false__";
270
+ } else if (value && typeof value === 'object' && !Array.isArray(value)) {
271
+ return convertBooleansToMarkers(value);
272
+ } else if (value && Array.isArray(value)) {
273
+ return value.map(convertValueBooleansToMarkers);
274
+ }
275
+ return value;
276
+ }
277
+
278
+ export {createCustomPurchaseConfig, HELIUM_CTA_NAMES} from './HeliumPaywallSdk.types';
@@ -1,6 +1,7 @@
1
1
  import Purchases, {PURCHASES_ERROR_CODE, PurchasesStoreProduct} from 'react-native-purchases';
2
2
  import type { PurchasesError, PurchasesPackage, CustomerInfoUpdateListener, CustomerInfo, PurchasesEntitlementInfo } from 'react-native-purchases';
3
3
  import {HeliumPurchaseConfig, HeliumPurchaseResult} from "../HeliumPaywallSdk.types";
4
+ import {setRevenueCatAppUserId} from "../index";
4
5
 
5
6
  // Rename the factory function
6
7
  export function createRevenueCatPurchaseConfig(config?: {
@@ -8,7 +9,6 @@ export function createRevenueCatPurchaseConfig(config?: {
8
9
  }): HeliumPurchaseConfig {
9
10
  const rcHandler = new RevenueCatHeliumHandler(config?.apiKey);
10
11
  return {
11
- apiKey: config?.apiKey,
12
12
  makePurchase: rcHandler.makePurchase.bind(rcHandler),
13
13
  restorePurchases: rcHandler.restorePurchases.bind(rcHandler),
14
14
  };
@@ -24,9 +24,8 @@ export class RevenueCatHeliumHandler {
24
24
  constructor(apiKey?: string) {
25
25
  if (apiKey) {
26
26
  Purchases.configure({ apiKey });
27
- } else {
28
27
  }
29
- this.initializePackageMapping();
28
+ void this.initializePackageMapping();
30
29
  }
31
30
 
32
31
  private async initializePackageMapping(): Promise<void> {
@@ -35,6 +34,9 @@ export class RevenueCatHeliumHandler {
35
34
  }
36
35
  this.initializationPromise = (async () => {
37
36
  try {
37
+ // Keep this value as up-to-date as possible
38
+ setRevenueCatAppUserId(await Purchases.getAppUserID());
39
+
38
40
  const offerings = await Purchases.getOfferings();
39
41
  const allOfferings = offerings.all;
40
42
  for (const offering of Object.values(allOfferings)) {
@@ -64,6 +66,8 @@ export class RevenueCatHeliumHandler {
64
66
 
65
67
  async makePurchase(productId: string): Promise<HeliumPurchaseResult> {
66
68
  await this.ensureMappingInitialized();
69
+ // Keep this value as up-to-date as possible
70
+ setRevenueCatAppUserId(await Purchases.getAppUserID());
67
71
 
68
72
  const pkg: PurchasesPackage | undefined = this.productIdToPackageMapping[productId];
69
73
  let rcProduct: PurchasesStoreProduct | undefined;