diff --git a/android/src/main/java/com/revenuecat/purchases/react/RNPurchasesModule.java b/android/src/main/java/com/revenuecat/purchases/react/RNPurchasesModule.java index b345519f3..353e7962e 100644 --- a/android/src/main/java/com/revenuecat/purchases/react/RNPurchasesModule.java +++ b/android/src/main/java/com/revenuecat/purchases/react/RNPurchasesModule.java @@ -588,6 +588,33 @@ public void redeemWebPurchase(String urlString, final Promise promise) { CommonKt.redeemWebPurchase(urlString, getOnResult(promise)); } + // region Ad Tracking + + @ReactMethod + public void trackAdFailedToLoad(ReadableMap adData) { + CommonKt.trackAdFailedToLoad(adData.toHashMap()); + } + + @ReactMethod + public void trackAdLoaded(ReadableMap adData) { + CommonKt.trackAdLoaded(adData.toHashMap()); + } + + @ReactMethod + public void trackAdDisplayed(ReadableMap adData) { + CommonKt.trackAdDisplayed(adData.toHashMap()); + } + + @ReactMethod + public void trackAdOpened(ReadableMap adData) { + CommonKt.trackAdOpened(adData.toHashMap()); + } + + @ReactMethod + public void trackAdRevenue(ReadableMap adData) { + CommonKt.trackAdRevenue(adData.toHashMap()); + } + // endregion //================================================================================ diff --git a/ios/RNPurchases.m b/ios/RNPurchases.m index 2333929bf..392bd8464 100644 --- a/ios/RNPurchases.m +++ b/ios/RNPurchases.m @@ -337,6 +337,28 @@ static void logUnavailablePresentCodeRedemptionSheet() { } +#pragma mark - Ad Tracking + +RCT_EXPORT_METHOD(trackAdFailedToLoad:(NSDictionary *)adData) { + [RCCommonFunctionality trackAdFailedToLoad:adData.mappingNSNullToNil]; +} + +RCT_EXPORT_METHOD(trackAdLoaded:(NSDictionary *)adData) { + [RCCommonFunctionality trackAdLoaded:adData.mappingNSNullToNil]; +} + +RCT_EXPORT_METHOD(trackAdDisplayed:(NSDictionary *)adData) { + [RCCommonFunctionality trackAdDisplayed:adData.mappingNSNullToNil]; +} + +RCT_EXPORT_METHOD(trackAdOpened:(NSDictionary *)adData) { + [RCCommonFunctionality trackAdOpened:adData.mappingNSNullToNil]; +} + +RCT_EXPORT_METHOD(trackAdRevenue:(NSDictionary *)adData) { + [RCCommonFunctionality trackAdRevenue:adData.mappingNSNullToNil]; +} + #pragma mark - Subscriber Attributes RCT_EXPORT_METHOD(setProxyURLString:(nullable NSString *)proxyURLString diff --git a/src/ads.ts b/src/ads.ts new file mode 100644 index 000000000..bd02cfc26 --- /dev/null +++ b/src/ads.ts @@ -0,0 +1,132 @@ +/** + * Ad tracking types for reporting ad events to RevenueCat. + * + * These types map to the native SDK's ad tracking API, enabling comprehensive + * LTV tracking across subscriptions and ad monetization. + */ + +/** + * Predefined ad mediator names. You can also use custom string values. + */ +export const AD_MEDIATOR_NAME = { + AD_MOB: "AdMob", + APP_LOVIN: "AppLovin", +} as const; + +/** + * Predefined ad format types. + */ +export const AD_FORMAT = { + OTHER: "other", + BANNER: "banner", + INTERSTITIAL: "interstitial", + REWARDED: "rewarded", + REWARDED_INTERSTITIAL: "rewarded_interstitial", + NATIVE: "native", + APP_OPEN: "app_open", + MREC: "mrec", +} as const; + +/** + * Revenue precision levels for ad revenue reporting. + */ +export const AD_REVENUE_PRECISION = { + EXACT: "exact", + PUBLISHER_DEFINED: "publisher_defined", + ESTIMATED: "estimated", + UNKNOWN: "unknown", +} as const; + +/** + * Data for tracking a failed ad load event. + */ +export interface AdFailedToLoadData { + /** The mediation SDK name (e.g. "AdMob", "AppLovin") */ + mediatorName: string; + /** The ad format (e.g. "banner", "interstitial") */ + adFormat: string; + /** The ad unit identifier */ + adUnitId: string; + /** Optional placement identifier */ + placement?: string | null; + /** Optional error code from the mediation SDK */ + mediatorErrorCode?: number | null; +} + +/** + * Data for tracking a successful ad load event. + */ +export interface AdLoadedData { + /** The ad network name */ + networkName?: string | null; + /** The mediation SDK name */ + mediatorName: string; + /** The ad format */ + adFormat: string; + /** The ad unit identifier */ + adUnitId: string; + /** Unique impression identifier */ + impressionId: string; + /** Optional placement identifier */ + placement?: string | null; +} + +/** + * Data for tracking an ad display/impression event. + */ +export interface AdDisplayedData { + /** The ad network name */ + networkName?: string | null; + /** The mediation SDK name */ + mediatorName: string; + /** The ad format */ + adFormat: string; + /** The ad unit identifier */ + adUnitId: string; + /** Unique impression identifier */ + impressionId: string; + /** Optional placement identifier */ + placement?: string | null; +} + +/** + * Data for tracking an ad opened/clicked event. + */ +export interface AdOpenedData { + /** The ad network name */ + networkName?: string | null; + /** The mediation SDK name */ + mediatorName: string; + /** The ad format */ + adFormat: string; + /** The ad unit identifier */ + adUnitId: string; + /** Unique impression identifier */ + impressionId: string; + /** Optional placement identifier */ + placement?: string | null; +} + +/** + * Data for tracking ad revenue. + */ +export interface AdRevenueData { + /** The ad network name */ + networkName?: string | null; + /** The mediation SDK name */ + mediatorName: string; + /** The ad format */ + adFormat: string; + /** The ad unit identifier */ + adUnitId: string; + /** Unique impression identifier */ + impressionId: string; + /** Optional placement identifier */ + placement?: string | null; + /** Revenue in micro-units (e.g. 1500000 = $1.50) */ + revenueMicros: number; + /** ISO 4217 currency code (e.g. "USD") */ + currency: string; + /** Revenue accuracy level */ + precision: string; +} diff --git a/src/index.ts b/src/index.ts index 947501548..738098c54 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,3 +5,4 @@ export * from './errors'; export * from './customerInfo'; export * from './purchases'; export * from './offerings'; +export * from './ads'; diff --git a/src/purchases.ts b/src/purchases.ts index d973d23a6..3cac711c9 100644 --- a/src/purchases.ts +++ b/src/purchases.ts @@ -1,4 +1,11 @@ import { NativeEventEmitter, NativeModules } from "react-native"; +import type { + AdFailedToLoadData, + AdLoadedData, + AdDisplayedData, + AdOpenedData, + AdRevenueData, +} from "./ads"; import { PurchasesError, PURCHASES_ERROR_CODE, @@ -1551,6 +1558,57 @@ export default class Purchases { RNPurchases.overridePreferredLocale(locale); } + // region Ad Tracking + + /** + * Tracks when an ad fails to load. Call this from your ad SDK's failure callback. + * @param {AdFailedToLoadData} data The failed-to-load event data + */ + public static async trackAdFailedToLoad( + data: AdFailedToLoadData + ): Promise { + await Purchases.throwIfNotConfigured(); + RNPurchases.trackAdFailedToLoad(data); + } + + /** + * Tracks when an ad successfully loads. + * @param {AdLoadedData} data The ad loaded event data + */ + public static async trackAdLoaded(data: AdLoadedData): Promise { + await Purchases.throwIfNotConfigured(); + RNPurchases.trackAdLoaded(data); + } + + /** + * Tracks when an ad is displayed (impression). + * @param {AdDisplayedData} data The ad displayed event data + */ + public static async trackAdDisplayed(data: AdDisplayedData): Promise { + await Purchases.throwIfNotConfigured(); + RNPurchases.trackAdDisplayed(data); + } + + /** + * Tracks when an ad is opened/clicked. + * @param {AdOpenedData} data The ad opened event data + */ + public static async trackAdOpened(data: AdOpenedData): Promise { + await Purchases.throwIfNotConfigured(); + RNPurchases.trackAdOpened(data); + } + + /** + * Tracks ad revenue. Use this to report ad revenue alongside subscription data. + * @param {AdRevenueData} data The ad revenue event data + */ + public static async trackAdRevenue(data: AdRevenueData): Promise { + await Purchases.throwIfNotConfigured(); + RNPurchases.trackAdRevenue(data); + } + + // endregion + /** * Check if billing is supported for the current user (meaning IN-APP purchases are supported) * and optionally, whether a list of specified feature types are supported.