diff --git a/Docs/API.md b/Docs/API.md index 4d795e08..d7921d24 100755 --- a/Docs/API.md +++ b/Docs/API.md @@ -677,3 +677,105 @@ appsFlyer.setDeviceTrackingDisabled(true, () => {}); ``` --- +##### **`setOneLinkCustomDomains(domains, successC, errorC)`** + + Set Onelink custom/branded domains
+ Use this API during the SDK Initialization to indicate branded domains.
+ For more information please refer to the [documentation](https://support.appsflyer.com/hc/en-us/articles/360002329137-Implementing-Branded-Links) + +| parameter | type | description | +| ---------- |----------|------------------ | +| domains | array | Comma separated array of branded domains | +| successC | function | success callback | +| errorC | function | error callback | + + +*Example:* + +```javascript +appsFlyer.setOneLinkCustomDomains(["click.mybrand.com"], + (res) => { + console.log(res); + }, (error) => { + console.log(error); + }); +``` +--- + +#####
**`setResolveDeepLinkURLs(urls, successC, errorC)`** + +Set domains used by ESP when wrapping your deeplinks.
+Use this API during the SDK Initialization to indicate that links from certain domains should be resolved in order to get original deeplink
+For more information please refer to the [documentation](https://support.appsflyer.com/hc/en-us/articles/360001409618-Email-service-provider-challenges-with-iOS-Universal-links)
+ +| parameter | type | description | +| ---------- |----------|------------------ | +| urls | array | Comma separated array of ESP domains requiring resolving | +| successC | function | success callback | +| errorC | function | error callback | + + +*Example:* + +```javascript +appsFlyer.setResolveDeepLinkURLs(["click.esp-domain.com"], + (res) => { + console.log(res); + }, (error) => { + console.log(error); + }); +``` + +--- + +#####
**`performOnAppAttribution(url, callback)`** + +This function allows developers to manually re-trigger onAppOpenAttribution with a specific link (URI or URL), **without recording a new re-engagement**.
+This method may be required if the app needs to redirect users based on the given link, or resolve the AppsFlyer short URL while staying in the foreground/opened. This might be needed because regular onAppOpenAttribution callback is only called if the app was opened with the deep link. + +| parameter | type | description | +| ---------- |----------|------------------ | +| url | string | String representing the URL that needs to be resolved/returned in the onAppOpenAttribution callback | +| callback | function | Result callback | + + +*Example:* + +```javascript + let uriString = "sdktest://test" + appsFlyer.performOnAppAttribution(uriString, (res) => { + console.log(res); + }) +``` +--- +#####
**`setSharingFilterForAllPartners()`** + +Used by advertisers to exclude **all** networks/integrated partners from getting data. Learn more [here](https://support.appsflyer.com/hc/en-us/articles/207032126#additional-apis-exclude-partners-from-getting-data) + +*Example:* + +```javascript + appsFlyer.setSharingFilterForAllPartners() +``` +--- +##### **`setSharingFilter(partners, sucessC, errorC)`** + +Used by advertisers to exclude **specified** networks/integrated partners from getting data. Learn more [here](https://support.appsflyer.com/hc/en-us/articles/207032126#additional-apis-exclude-partners-from-getting-data) + +| parameter | type | description | +| ---------- |----------|------------------ | +| partners | array | Comma separated array of partners that need to be excluded | +| successC | function | success callback | +| errorC | function | error callback | +*Example:* + +```javascript + let partners = ["facebook_int","googleadwords_int","snapchat_int","doubleclick_int"] + appsFlyer.setSharingFilterForAllPartners(partners, + (res) => { + console.log(res); + }, (error) => { + console.log(error); + }) +``` +--- diff --git a/Docs/Guides.md b/Docs/Guides.md index de76fad7..4f36e381 100755 --- a/Docs/Guides.md +++ b/Docs/Guides.md @@ -108,8 +108,6 @@ The `appsFlyer.onAppOpenAttribution` returns function to unregister this event ### *Example:* ```javascript -import React, {useEffect, useState} from 'react'; -import {AppState, SafeAreaView, Text, View} from 'react-native'; import appsFlyer from 'react-native-appsflyer'; var onAppOpenAttributionCanceller = appsFlyer.onAppOpenAttribution((res) => { @@ -148,18 +146,8 @@ appsFlyer.initSdk( // ... class App extends Component<{}> { - state = { - appState: AppState.currentState, - }; - - componentDidMount() { - AppState.addEventListener('change', this._handleAppStateChange); - } - componentWillUnmount() { - AppState.removeEventListener('change', this._handleAppStateChange); - - // Optionaly remove listeners for deep link data if you no longer need them + // Optionaly remove listeners for deep link data if you no longer need them after componentWillUnmount if (onInstallConversionDataCanceller) { onInstallConversionDataCanceller(); console.log('unregister onInstallConversionDataCanceller'); @@ -171,15 +159,6 @@ class App extends Component<{}> { onAppOpenAttributionCanceller = null; } } - - _handleAppStateChange = (nextAppState) => { - if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') { - if (Platform.OS === 'ios') { - appsFlyer.trackAppLaunch(); - } - } - this.setState({appState: nextAppState}); - }; } ``` @@ -227,33 +206,19 @@ appsFlyer.initSdk( const Home = (props) => { - const [appState, setAppState] = useState(AppState.currentState); - useEffect(() => { - function handleAppStateChange(nextAppState) { - if (appState.match(/inactive|background/) && nextAppState === 'active') { - if (Platform.OS === 'ios') { - appsFlyer.trackAppLaunch(); - } + return () => { + // Optionaly remove listeners for deep link data if you no longer need them after componentWillUnmount + if (onInstallConversionDataCanceller) { + onInstallConversionDataCanceller(); + console.log('unregister onInstallConversionDataCanceller'); + onInstallConversionDataCanceller = null; } - if (appState.match(/active|foreground/) && nextAppState === 'background') { - if (onInstallConversionDataCanceller) { - onInstallConversionDataCanceller(); - onInstallConversionDataCanceller = null; - } - if (onAppOpenAttributionCanceller) { - onAppOpenAttributionCanceller(); - onAppOpenAttributionCanceller = null; - } + if (onAppOpenAttributionCanceller) { + onAppOpenAttributionCanceller(); + console.log('unregister onAppOpenAttributionCanceller'); + onAppOpenAttributionCanceller = null; } - - setAppState(nextAppState); - } - - AppState.addEventListener('change', handleAppStateChange); - - return () => { - AppState.removeEventListener('change', handleAppStateChange); }; }); diff --git a/README.md b/README.md index f59f6078..5d45199d 100755 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ ### This plugin is built for -- iOS AppsFlyerSDK **v5.2.0** -- Android AppsFlyerSDK **v5.2.0** +- iOS AppsFlyerSDK **v5.4.1** +- Android AppsFlyerSDK **v5.4.1** ## 📲 Adding the SDK to your project diff --git a/android/build.gradle b/android/build.gradle index 0c610433..68e4856a 100755 --- a/android/build.gradle +++ b/android/build.gradle @@ -30,5 +30,5 @@ repositories { dependencies { implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}" implementation "com.android.installreferrer:installreferrer:${safeExtGet('installReferrerVersion', '1.0')}" - implementation "com.appsflyer:af-android-sdk:${safeExtGet('appsflyerVersion', '5.2.0')}" + implementation "com.appsflyer:af-android-sdk:${safeExtGet('appsflyerVersion', '5.4.1')}" } diff --git a/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerConstants.java b/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerConstants.java index fa2023cd..01ee3947 100755 --- a/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerConstants.java +++ b/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerConstants.java @@ -10,7 +10,8 @@ public class RNAppsFlyerConstants { final static String UNKNOWN_ERROR = "AF Unknown Error"; final static String SUCCESS = "Success"; final static String NO_EVENT_NAME_FOUND = "No 'eventName' found or its empty"; - final static String NO_EMAILS_FOUND_OR_CORRUPTED = "No 'emails' found, or list is corrupted"; + final static String EMPTY_OR_CORRUPTED_LIST = "No arguments found or list is corrupted"; + final static String INVALID_URI = "Passed string is not a valid URI"; final static String afIsDebug = "isDebug"; diff --git a/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java b/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java index 53d1049d..74b4fa11 100755 --- a/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java +++ b/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java @@ -23,15 +23,18 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableArray; import com.facebook.react.modules.core.DeviceEventManagerModule; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.net.URI; import java.util.HashMap; import java.util.Map; import java.util.Iterator; +import java.util.ArrayList; import static com.appsflyer.reactnative.RNAppsFlyerConstants.*; @@ -135,18 +138,14 @@ private String callSdkInternal(ReadableMap _options) { Activity currentActivity = getCurrentActivity(); if (currentActivity != null) { - intent = currentActivity.getIntent(); + // register for lifecycle with Activity (automatically fetching deeplink from Activity if present) + instance.startTracking(currentActivity, devKey); + } else { + // register for lifecycle with Application (cannot fetch deeplink without access to the Activity, + // also sending first session manually) + trackAppLaunch(); + instance.startTracking(application, devKey); } - - //Generally we already do this validation into the SDK, anyways, we want to show it to clients - if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) { - AppsFlyerLib.getInstance().setPluginDeepLinkData(intent); - } - - trackAppLaunch(); - instance.startTracking(application, devKey); - - return null; } @@ -402,7 +401,7 @@ public void setUserEmails(ReadableMap _options, JSONArray emailsJSON = options.optJSONArray(afEmails); if (emailsJSON.length() == 0) { - errorCallback.invoke(new Exception(NO_EMAILS_FOUND_OR_CORRUPTED).getMessage()); + errorCallback.invoke(new Exception(EMPTY_OR_CORRUPTED_LIST).getMessage()); return; } @@ -422,7 +421,7 @@ public void setUserEmails(ReadableMap _options, } } catch (JSONException e) { e.printStackTrace(); - errorCallback.invoke(new Exception(NO_EMAILS_FOUND_OR_CORRUPTED).getMessage()); + errorCallback.invoke(new Exception(EMPTY_OR_CORRUPTED_LIST).getMessage()); return; } @@ -562,4 +561,73 @@ public void setDeviceTrackingDisabled(boolean b, Callback callback){ AppsFlyerLib.getInstance().setDeviceTrackingDisabled(b); callback.invoke(SUCCESS); } + + @ReactMethod + public void setOneLinkCustomDomains(ReadableArray domainsArray, Callback successCallback, Callback errorCallback) { + if (domainsArray.size() <= 0) { + errorCallback.invoke(EMPTY_OR_CORRUPTED_LIST); + return; + } + ArrayList domainsList = domainsArray.toArrayList(); + try { + String[] domains = domainsList.toArray(new String[domainsList.size()]); + AppsFlyerLib.getInstance().setOneLinkCustomDomain(domains); + successCallback.invoke(SUCCESS); + } catch (Exception e) { + e.printStackTrace(); + errorCallback.invoke(EMPTY_OR_CORRUPTED_LIST); + } + } + + @ReactMethod + public void setResolveDeepLinkURLs(ReadableArray urlsArray, Callback successCallback, Callback errorCallback) { + if (urlsArray.size() <= 0) { + errorCallback.invoke(EMPTY_OR_CORRUPTED_LIST); + return; + } + ArrayList urlsList = urlsArray.toArrayList(); + try { + String[] urls = urlsList.toArray(new String[urlsList.size()]); + AppsFlyerLib.getInstance().setResolveDeepLinkURLs(urls); + successCallback.invoke(SUCCESS); + } catch (Exception e) { + e.printStackTrace(); + errorCallback.invoke(EMPTY_OR_CORRUPTED_LIST); + } + } + + @ReactMethod + public void performOnAppAttribution(String urlString, Callback callback) { + try { + URI uri = URI.create(urlString); + Context c = application.getApplicationContext(); + AppsFlyerLib.getInstance().performOnAppAttribution(c, uri); + callback.invoke(SUCCESS); + } catch (Exception e) { + e.printStackTrace(); + callback.invoke(INVALID_URI); + } + } + + @ReactMethod + public void setSharingFilterForAllPartners() { + AppsFlyerLib.getInstance().setSharingFilterForAllPartners(); + } + + @ReactMethod + public void setSharingFilter(ReadableArray partnersArray, Callback successCallback, Callback errorCallback) { + if (partnersArray.size() <= 0) { + errorCallback.invoke(EMPTY_OR_CORRUPTED_LIST); + return; + } + ArrayList partnersList = partnersArray.toArrayList(); + try { + String[] partners = partnersList.toArray(new String[partnersList.size()]); + AppsFlyerLib.getInstance().setSharingFilter(partners); + successCallback.invoke(SUCCESS); + } catch (Exception e) { + e.printStackTrace(); + errorCallback.invoke(EMPTY_OR_CORRUPTED_LIST); + } + } } diff --git a/index.d.ts b/index.d.ts index 7865e003..d04b009e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -54,22 +54,27 @@ declare module "react-native-appsflyer" { initSdk(options:InitSDKOptions, successC?:SuccessCB, errorC?:ErrorCB): Response trackEvent(eventName:string, eventValues:object, successC?:SuccessCB, errorC?:ErrorCB): Response setUserEmails(options:SetEmailsOptions, successC?:SuccessCB, errorC?:ErrorCB): void - setAdditionalData(additionalData:object, successC?:SuccessCB): void - getAppsFlyerUID(callback:(error:Error, uid:string)=>any): void - setCustomerUserId(userId:string, successC?:SuccessCB): void - stopTracking(isStopTracking:boolean, successC?:SuccessCB): void - setAppInviteOneLinkID(oneLinkID:string, successC?:SuccessCB): void - generateInviteLink(params:GenerateInviteLinkParams, successC?:SuccessCB, errorC?:ErrorCB): void - trackCrossPromotionImpression(appId:string, campaign:string): void - trackAndOpenStore(appId:string, campaign:string, params: object): void - setCurrencyCode(currencyCode:string, successC:SuccessCB): void - setDeviceTrackingDisabled(isDeviceTrackingDisabled:boolean, successC:SuccessCB): void + setAdditionalData(additionalData: object, successC?: SuccessCB): void + getAppsFlyerUID(callback: (error: Error, uid: string) => any): void + setCustomerUserId(userId: string, successC?: SuccessCB): void + stopTracking(isStopTracking: boolean, successC?: SuccessCB): void + setAppInviteOneLinkID(oneLinkID: string, successC?: SuccessCB): void + generateInviteLink(params: GenerateInviteLinkParams, successC?: SuccessCB, errorC?: ErrorCB): void + trackCrossPromotionImpression(appId: string, campaign: string): void + trackAndOpenStore(appId: string, campaign: string, params: object): void + setCurrencyCode(currencyCode: string, successC: SuccessCB): void + setDeviceTrackingDisabled(isDeviceTrackingDisabled: boolean, successC: SuccessCB): void + setOneLinkCustomDomains(domains: string[], successC?: SuccessCB, errorC?: ErrorCB): void + setResolveDeepLinkURLs(urls: string[], successC?: SuccessCB, errorC?: ErrorCB): void + performOnAppAttribution(urlString, callback): void + setSharingFilterForAllPartners(): void + setSharingFilter(partners, successC, errorC): void /** * For iOS Only * */ trackAppLaunch(): void - trackLocation(longitude:number, latitude:number, callback:SuccessCB): void + trackLocation(longitude: number, latitude: number, callback: SuccessCB): void /** * For Android Only diff --git a/index.js b/index.js index af32e820..c8f8cab3 100755 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -import { NativeEventEmitter, NativeModules } from "react-native"; +import {NativeEventEmitter, NativeModules} from "react-native"; const { RNAppsFlyer } = NativeModules; const appsFlyer = {}; @@ -330,13 +330,50 @@ appsFlyer.onAppOpenAttribution = callback => { * Anonymize user Data. * Use this API during the SDK Initialization to explicitly anonymize a user's installs, events and sessions. * Default is false - * @param isDeviceTrackingDisabled boolean + * @param isDeviceTrackingDisabled boolean * @param successC success callback function. */ appsFlyer.setDeviceTrackingDisabled = (isDeviceTrackingDisabled, successC) => { return RNAppsFlyer.setDeviceTrackingDisabled(isDeviceTrackingDisabled, successC); }; +/** + * Set Onelink custom/branded domains + * Use this API during the SDK Initialization to indicate branded domains. + * For more information please refer to https://support.appsflyer.com/hc/en-us/articles/360002329137-Implementing-Branded-Links + * @param domains array of strings + * @param successC success callback function. + * @param errorC error callback function. + */ +appsFlyer.setOneLinkCustomDomains = (domains, successC, errorC) => { + return RNAppsFlyer.setOneLinkCustomDomains(domains, successC, errorC); +}; + +/** + * Set domains used by ESP when wrapping your deeplinks. + * Use this API during the SDK Initialization to indicate that links from certain domains should be resolved + * in order to get original deeplink + * For more information please refer to https://support.appsflyer.com/hc/en-us/articles/360001409618-Email-service-provider-challenges-with-iOS-Universal-links + * @param urls array of strings + * @param successC success callback function. + * @param errorC error callback function. + */ +appsFlyer.setResolveDeepLinkURLs = (urls, successC, errorC) => { + return RNAppsFlyer.setResolveDeepLinkURLs(urls, successC, errorC); +}; + +appsFlyer.performOnAppAttribution = (urlString, callback) => { + return RNAppsFlyer.performOnAppAttribution(urlString, callback); +} + +appsFlyer.setSharingFilterForAllPartners = () => { + return RNAppsFlyer.setSharingFilterForAllPartners(); +} + +appsFlyer.setSharingFilter = (partners, successC, errorC) => { + return RNAppsFlyer.setSharingFilter(partners, successC, errorC); +} + function AFParseJSONException(_message, _data) { this.message = _message; this.data = _data; diff --git a/ios/RNAppsFlyer.h b/ios/RNAppsFlyer.h index 03b61895..e3718bdd 100755 --- a/ios/RNAppsFlyer.h +++ b/ios/RNAppsFlyer.h @@ -17,7 +17,7 @@ static NSString *const NO_DEVKEY_FOUND = @"No 'devKey' found or its empty"; static NSString *const NO_APPID_FOUND = @"No 'appId' found or its empty"; static NSString *const NO_EVENT_NAME_FOUND = @"No 'eventName' found or its empty"; -static NSString *const NO_EMAILS_FOUND_OR_CORRUPTED = @"No 'emails' found, or list is corrupted"; +static NSString *const EMPTY_OR_CORRUPTED_LIST = @"No arguments found or list is corrupted"; static NSString *const SUCCESS = @"Success"; // Appsflyer JS objects diff --git a/ios/RNAppsFlyer.m b/ios/RNAppsFlyer.m index c6daf4e9..72736bd0 100755 --- a/ios/RNAppsFlyer.m +++ b/ios/RNAppsFlyer.m @@ -131,7 +131,7 @@ @implementation RNAppsFlyer NSError* error = nil; if (!emails || [emails count] == 0) { - error = [NSError errorWithDomain:NO_EMAILS_FOUND_OR_CORRUPTED code:0 userInfo:nil]; + error = [NSError errorWithDomain:EMPTY_OR_CORRUPTED_LIST code:0 userInfo:nil]; } if(error != nil){ @@ -192,10 +192,19 @@ -(NSError *) callSdkInternal:(NSDictionary*)initSdkOptions { [AppsFlyerTracker sharedTracker].isDebug = isDebug; [[AppsFlyerTracker sharedTracker] trackAppLaunch]; + // Register for background-foreground transitions natively instead of doing this in JavaScript + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(sendLaunch:) + name:UIApplicationDidBecomeActiveNotification + object:nil]; return nil; } } +-(void)sendLaunch:(UIApplication *)application { + [[AppsFlyerTracker sharedTracker] trackAppLaunch]; +} + -(NSError *) trackEventInternal: (NSString *)eventName eventValues:(NSDictionary *)eventValues { if (!eventName || [eventName isEqualToString:@""]) { @@ -406,4 +415,36 @@ -(void) reportOnSuccess:(NSString *)data type:(NSString*) type { callback(@[SUCCESS]); } +RCT_EXPORT_METHOD(setOneLinkCustomDomains:(NSArray *) domains + successCallback :(RCTResponseSenderBlock)successCallback + errorCallback:(RCTResponseErrorBlock)errorCallback) { + [[AppsFlyerTracker sharedTracker] setOneLinkCustomDomains:domains]; + successCallback(@[SUCCESS]); +} + +RCT_EXPORT_METHOD(setResolveDeepLinkURLs:(NSArray *) urls + successCallback :(RCTResponseSenderBlock)successCallback + errorCallback:(RCTResponseErrorBlock)errorCallback) { + [[AppsFlyerTracker sharedTracker] setResolveDeepLinkURLs:urls]; + successCallback(@[SUCCESS]); +} + +RCT_EXPORT_METHOD(performOnAppAttribution:(NSString *) urlString + callback :(RCTResponseSenderBlock)callback) { + NSURL *url = [NSURL URLWithString:urlString]; + [[AppsFlyerTracker sharedTracker] performOnAppAttributionWithURL:url]; + callback(@[SUCCESS]); +} + +RCT_EXPORT_METHOD(setSharingFilterForAllPartners) { + [[AppsFlyerTracker sharedTracker] setSharingFilterForAllPartners]; +} + +RCT_EXPORT_METHOD(setSharingFilter:(NSArray *)partners + successCallback:(RCTResponseSenderBlock)successCallback + errorCallback:(RCTResponseErrorBlock)errorCallback) { + [[AppsFlyerTracker sharedTracker] setSharingFilter:partners]; + successCallback(@[SUCCESS]); +} + @end diff --git a/package.json b/package.json index f301a40e..914024b3 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-appsflyer", - "version": "5.2.0", + "version": "5.4.1", "description": "React Native Appsflyer plugin", "main": "index.js", "scripts": { diff --git a/react-native-appsflyer.podspec b/react-native-appsflyer.podspec index 29d69591..e45b3de2 100644 --- a/react-native-appsflyer.podspec +++ b/react-native-appsflyer.podspec @@ -13,6 +13,6 @@ Pod::Spec.new do |s| s.source_files = 'ios/**/*.{h,m}' s.platform = :ios, "8.0" s.static_framework = true - s.dependency 'AppsFlyerFramework', '~> 5.2.0' + s.dependency 'AppsFlyerFramework', '~> 5.4.1' s.dependency 'React' end