diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fc48a90..c303dd1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,7 @@ # Versions +## 6.1.20 +- Unified deep linking + ## 6.1.10 - iOS SDK 6.1.1 - Android SDK 6.1.0 diff --git a/Docs/API.md b/Docs/API.md index a936bd8b..9a835e6e 100755 --- a/Docs/API.md +++ b/Docs/API.md @@ -51,7 +51,7 @@ The dev key is required for all apps and the appID is required only for iOS.
{ console.log(res); diff --git a/Docs/Guides.md b/Docs/Guides.md index 3747c819..a1e6b9f1 100755 --- a/Docs/Guides.md +++ b/Docs/Guides.md @@ -8,6 +8,7 @@ - [Deep Linking](#deeplinking) - [Deferred Deep Linking (Get Conversion Data)](#deferred-deep-linking) - [Direct Deep Linking](#direct-deep-linking) + - [Unified deep linking](#Unified-deep-linking) - [iOS Deeplink Setup](#iosdeeplinks) - [Android Deeplink Setup](#android-deeplinks) - [Uninstall](#measure-app-uninstalls) @@ -41,12 +42,12 @@ appsFlyer.initSdk( -#### The 2 Deep Linking Types: -Since users may or may not have the mobile app installed, there are 2 types of deep linking: +#### The 3 Deep Linking Types: +Since users may or may not have the mobile app installed, there are 3 types of deep linking: 1. Deferred Deep Linking - Serving personalized content to new or former users, directly after the installation. 2. Direct Deep Linking - Directly serving personalized content to existing users, which already have the mobile app installed. - +3. Unified deep linking - Unified deep linking sends new and existing users to a specific in-app activity as soon as the app is opened.
For more info please check out the [OneLink™ Deep Linking Guide](https://support.appsflyer.com/hc/en-us/articles/208874366-OneLink-Deep-Linking-Guide#Intro). ### 1. Deferred Deep Linking (Get Conversion Data) @@ -103,6 +104,45 @@ appsFlyer.initSdk(/*...*/); The `appsFlyer.onAppOpenAttribution` returns function to unregister this event listener. If you want to remove the listener for any reason, you can simply call `onAppOpenAttributionCanceller()`. This function will call `NativeAppEventEmitter.remove()`. +
+ +###
3. Unified deep linking +In order to use the unified deep link you need to send the `onDeepLinkListener: true` flag inside the object that sent to the sdk.
+**NOTE:** when sending this flag, the sdk will ignore `onAppOpenAttribution`!
+For more information about this api, please check [OneLink Guide Here](https://dev.appsflyer.com/docs/android-unified-deep-linking) + + +```javascript +var onDeepLinkCanceller = appsFlyer.onDeepLink(res => { + console.log('onDeepLinking: ' + JSON.stringify(res)); + console.log('status: '+ res.status); + console.log('type: '+ res.type); +}) + +appsFlyer.initSdk( + { + devKey: 'K2***********99', + isDebug: false, + appId: '41*****44', + onInstallConversionDataListener: true, + onDeepLinkListener: true + }, + (result) => { + console.log(result); + }, + (error) => { + console.error(error); + } +); +``` + +**Note:** The code implementation for `onDeepLink` must be made **prior to the initialization** code of the SDK. + +**Important** + +The `appsFlyer.onDeepLink` returns function to unregister this event listener. If you want to remove the listener for any reason, you can simply call `onDeepLinkCanceller()`. This function will call `NativeAppEventEmitter.remove()`. + +
### *Example:* @@ -114,6 +154,10 @@ var onAppOpenAttributionCanceller = appsFlyer.onAppOpenAttribution((res) => { console.log(res); }); +var onDeepLinkCanceller = appsFlyer.onDeepLink(res => { + console.log('onDeepLinking: ' + JSON.stringify(res)); +}) + var onInstallConversionDataCanceller = appsFlyer.onInstallConversionData( (res) => { if (JSON.parse(res.data.is_first_launch) == true) { @@ -158,6 +202,11 @@ class App extends Component<{}> { console.log('unregister onAppOpenAttributionCanceller'); onAppOpenAttributionCanceller = null; } + if (onDeepLinkCanceller) { + onDeepLinkCanceller(); + console.log('unregister onDeepLinkCanceller'); + onDeepLinkCanceller = null; + } } } ``` diff --git a/README.md b/README.md index 6994b38c..3b3c2ac6 100755 --- a/README.md +++ b/README.md @@ -117,7 +117,8 @@ appsFlyer.initSdk( devKey: 'K2***********99', isDebug: false, appId: '41*****44', - onInstallConversionDataListener: true, + onInstallConversionDataListener: true, //Optional + onDeepLinkListener: true, //Optional }, (result) => { console.log(result); @@ -134,6 +135,7 @@ appsFlyer.initSdk( | appId | Your iTunes [application ID](https://support.appsflyer.com/hc/en-us/articles/207377436-Adding-a-new-app#available-in-the-app-store-google-play-store-windows-phone-store) (iOS only) | | isDebug | Debug mode - set to `true` for testing only | |onInstallConversionDataListener| Set listener for SDK init response (Optional. default=true) | +|onDeepLinkListener| Set listener for DDL response (Optional. default=false) | ##
📖 Guides diff --git a/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerConstants.java b/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerConstants.java index a8566aa9..973d6f23 100755 --- a/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerConstants.java +++ b/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerConstants.java @@ -20,6 +20,7 @@ public class RNAppsFlyerConstants { final static String afEmails = "emails"; final static String afConversionData = "onInstallConversionDataListener"; + final static String afDeepLink = "onDeepLinkListener"; final static String afSuccess = "success"; final static String afFailure = "failure"; @@ -27,6 +28,7 @@ public class RNAppsFlyerConstants { final static String afOnAppOpenAttribution = "onAppOpenAttribution"; final static String afOnInstallConversionFailure = "onInstallConversionFailure"; final static String afOnInstallConversionDataLoaded = "onInstallConversionDataLoaded"; + final static String afOnDeepLinking = "onDeepLinking"; final static String INVITE_FAIL = "Could not create invite link"; final static String INVITE_CHANNEL = "channel"; diff --git a/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java b/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java index bcb0d89e..d45afc87 100755 --- a/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java +++ b/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java @@ -8,6 +8,11 @@ import android.net.Uri; import android.util.Log; +import androidx.annotation.NonNull; +import com.appsflyer.reactnative.RNUtil; +import com.appsflyer.deeplink.DeepLink; +import com.appsflyer.deeplink.DeepLinkListener; +import com.appsflyer.deeplink.DeepLinkResult; import com.appsflyer.*; import com.appsflyer.AFInAppEventType; import com.appsflyer.AppsFlyerConversionListener; @@ -38,6 +43,7 @@ import java.util.ArrayList; import static com.appsflyer.reactnative.RNAppsFlyerConstants.*; +import static com.appsflyer.reactnative.RNAppsFlyerConstants.afOnDeepLinking; public class RNAppsFlyerModule extends ReactContextBaseJavaModule { @@ -120,6 +126,7 @@ private String callSdkInternal(ReadableMap _options) { String devKey; boolean isDebug; boolean isConversionData; + boolean isDeepLinking; AppsFlyerLib instance = AppsFlyerLib.getInstance(); @@ -136,8 +143,13 @@ private String callSdkInternal(ReadableMap _options) { if (isDebug == true) { Log.d("AppsFlyer", "Starting SDK"); } + isDeepLinking = options.optBoolean(afDeepLink, false); + instance.init(devKey, (isConversionData == true) ? registerConversionListener() : null, application.getApplicationContext()); + if (isDeepLinking) { + instance.subscribeForDeepLink(registerDeepLinkListener()); + } Intent intent = null; Activity currentActivity = getCurrentActivity(); @@ -152,6 +164,32 @@ private String callSdkInternal(ReadableMap _options) { return null; } + private DeepLinkListener registerDeepLinkListener() { + return new DeepLinkListener() { + @Override + public void onDeepLinking(@NonNull DeepLinkResult deepLinkResult) { + DeepLinkResult.Error dlError = deepLinkResult.getError(); + if (dlError != null) { + sendEvent(reactContext, afOnDeepLinking, dlError.toString()); + } + JSONObject deepLinkObj = new JSONObject(); + try { + deepLinkObj.put("status", afSuccess); + deepLinkObj.put("deepLinkStatus", deepLinkResult.getStatus()); + deepLinkObj.put("type", afOnDeepLinking); + deepLinkObj.put("data", deepLinkResult.getDeepLink().toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + try { + sendEvent(reactContext, afOnDeepLinking,deepLinkObj.toString()); + } catch (Exception e) { + e.printStackTrace(); + } + } + }; + } + private AppsFlyerConversionListener registerConversionListener() { return new AppsFlyerConversionListener() { @@ -174,48 +212,49 @@ public void onConversionDataSuccess(Map conversionData) { public void onConversionDataFail(String errorMessage) { handleError(afOnInstallConversionFailure, errorMessage); } + }; + } - private void handleSuccess(String eventType, Map conversionData, Map attributionData) { - JSONObject obj = new JSONObject(); + private void handleSuccess(String eventType, Map conversionData, Map attributionData) { + JSONObject obj = new JSONObject(); - try { - JSONObject data = new JSONObject(conversionData == null ? attributionData : conversionData); - obj.put("status", afSuccess); - obj.put("type", eventType); - obj.put("data", data); - if (eventType.equals(afOnInstallConversionDataLoaded)) { - sendEvent(reactContext, afOnInstallConversionDataLoaded, obj.toString()); - } else if (eventType.equals(afOnAppOpenAttribution)) { - sendEvent(reactContext, afOnAppOpenAttribution, obj.toString()); - } - } catch (JSONException e) { - e.printStackTrace(); - } + try { + JSONObject data = new JSONObject(conversionData == null ? attributionData : conversionData); + obj.put("status", afSuccess); + obj.put("type", eventType); + obj.put("data", data); + if (eventType.equals(afOnInstallConversionDataLoaded)) { + sendEvent(reactContext, afOnInstallConversionDataLoaded, obj.toString()); + } else if (eventType.equals(afOnAppOpenAttribution)) { + sendEvent(reactContext, afOnAppOpenAttribution, obj.toString()); } + } catch (JSONException e) { + e.printStackTrace(); + } + } - private void handleError(String eventType, String errorMessage) { - JSONObject obj = new JSONObject(); + private void handleError(String eventType, String errorMessage) { + JSONObject obj = new JSONObject(); - try { - obj.put("status", afFailure); - obj.put("type", eventType); - obj.put("data", errorMessage); - sendEvent(reactContext, afOnInstallConversionDataLoaded, obj.toString()); - } catch (JSONException e) { - e.printStackTrace(); - } - } + try { + obj.put("status", afFailure); + obj.put("type", eventType); + obj.put("data", errorMessage); + sendEvent(reactContext, afOnInstallConversionDataLoaded, obj.toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + } - private void sendEvent(ReactContext reactContext, - String eventName, - Object params) { - reactContext - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit(eventName, params); - } - }; + private void sendEvent(ReactContext reactContext, + String eventName, + Object params) { + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(eventName, params); } + private String logEventInternal(final String eventName, ReadableMap eventData) { if (eventName.trim().equals("")) { diff --git a/index.d.ts b/index.d.ts index 7062b963..e4aedca0 100644 --- a/index.d.ts +++ b/index.d.ts @@ -10,7 +10,7 @@ declare module "react-native-appsflyer" { type: "onAppOpenAttribution" | "onInstallConversionDataLoaded" | "onAttributionFailure" - | "onInstallConversionFailure", + | "onInstallConversionFailure" data: { is_first_launch: "true" | "false"; media_source: string; @@ -30,6 +30,7 @@ declare module "react-native-appsflyer" { appId?: string; // iOS only isDebug?: boolean; onInstallConversionDataListener?: boolean; + onDeepLinkListener?: boolean; timeToWaitForATTUserAuthorization?: number; // iOS only } @@ -62,6 +63,7 @@ declare module "react-native-appsflyer" { onInstallConversionData(callback: (data: ConversionData) => any): () => void; onInstallConversionFailure(callback: (data: ConversionData) => any): () => void; onAppOpenAttribution(callback: (data: any) => any): () => void; + onDeepLink(callback: (data: any) => any): () => void; initSdk(options: InitSDKOptions): Promise; initSdk(options: InitSDKOptions, successC: SuccessCB, errorC: ErrorCB): void; logEvent(eventName: string, eventValues: object): Promise; diff --git a/index.js b/index.js index 797e8f37..a97c2d91 100755 --- a/index.js +++ b/index.js @@ -381,6 +381,31 @@ appsFlyer.onAppOpenAttribution = callback => { }; }; +appsFlyer.onDeepLink = callback => { + + const listener = appsFlyerEventEmitter.addListener( + "onDeepLinking", + _data => { + if (callback && typeof callback === typeof Function) { + try { + let data = JSON.parse(_data); + callback(data); + } catch (_error) { + callback(new AFParseJSONException("Invalid data structure", _data)); + } + } + } + ); + + + eventsMap["onDeepLinking"] = listener; + + // unregister listener (suppose should be called from componentWillUnmount() ) + return function remove() { + listener.remove(); + }; +}; + /** * Anonymize user Data. * Use this API during the SDK Initialization to explicitly anonymize a user's installs, events and sessions. diff --git a/ios/RNAppsFlyer.h b/ios/RNAppsFlyer.h index b8fdcaf8..669f6fa6 100755 --- a/ios/RNAppsFlyer.h +++ b/ios/RNAppsFlyer.h @@ -40,6 +40,8 @@ static NSString *const IOS_14_ONLY = @"Feature only supported o #define afOnAppOpenAttribution @"onAppOpenAttribution" #define afOnInstallConversionFailure @"onInstallConversionFailure" #define afOnInstallConversionDataLoaded @"onInstallConversionDataLoaded" + #define afDeepLink @"onDeepLinkListener" + #define afOnDeepLinking @"onDeepLinking" // User Invites, Cross Promotion #define afCpAppID @"crossPromotedAppId" diff --git a/ios/RNAppsFlyer.m b/ios/RNAppsFlyer.m index 9ec37815..cf69fe83 100755 --- a/ios/RNAppsFlyer.m +++ b/ios/RNAppsFlyer.m @@ -47,12 +47,14 @@ -(NSError *) callSdkInternal:(NSDictionary*)initSdkOptions { NSString* appId = nil; BOOL isDebug = NO; BOOL isConversionData = YES; + BOOL isDeepLinking = NO; NSNumber* interval = 0; if (![initSdkOptions isKindOfClass:[NSNull class]]) { id isDebugValue = nil; id isConversionDataValue = nil; + id isDeepLinkingValue = nil; devKey = (NSString*)[initSdkOptions objectForKey: afDevKey]; appId = (NSString*)[initSdkOptions objectForKey: afAppId]; interval = (NSNumber*)[initSdkOptions objectForKey: timeToWaitForATTUserAuthorization]; @@ -68,8 +70,12 @@ -(NSError *) callSdkInternal:(NSDictionary*)initSdkOptions { if ([isConversionDataValue isKindOfClass:[NSNumber class]]) { isConversionData = [(NSNumber*)isConversionDataValue boolValue]; } - } + isDeepLinkingValue = [initSdkOptions objectForKey: afDeepLink]; + if ([isDeepLinkingValue isKindOfClass:[NSNumber class]]) { + isDeepLinking = [(NSNumber*)isDeepLinkingValue boolValue]; + } +} NSError* error = nil; if (!devKey || [devKey isEqualToString:@""]) { @@ -87,6 +93,9 @@ -(NSError *) callSdkInternal:(NSDictionary*)initSdkOptions { if(isConversionData == YES){ [AppsFlyerLib shared].delegate = self; } + if(isDeepLinking == YES){ + [AppsFlyerLib shared].deepLinkDelegate = self; + } #ifndef AFSDK_NO_IDFA if (interval != 0 && interval != nil){ double timeoutInterval = [interval doubleValue]; @@ -290,6 +299,32 @@ -(NSError *) logEventInternal: (NSString *)eventName eventValues:(NSDictionary * } +- (void)didResolveDeepLink:(AppsFlyerDeepLinkResult* _Nonnull) result { + if(result.status != nil){ + NSString *deepLinkStatus = nil; + switch(result.status) { + case AFSDKDeepLinkResultStatusFound: + deepLinkStatus = @"FOUND"; + break; + case AFSDKDeepLinkResultStatusNotFound: + deepLinkStatus = @"NOT_FOUND"; + break; + case AFSDKDeepLinkResultStatusFailure: + deepLinkStatus = @"Error"; + break; + default: + [NSException raise:NSGenericException format:@"Unexpected FormatType."]; + } + NSDictionary* message = @{ + @"status": afSuccess, + @"deepLinkStatus": deepLinkStatus, + @"type": afOnDeepLinking, + @"data": result.deepLink.clickEvent + }; + [self performSelectorOnMainThread:@selector(handleCallback:) withObject:message waitUntilDone:NO]; + } +} + -(void)onConversionDataSuccess:(NSDictionary*) installData { NSDictionary* message = @{ @@ -370,7 +405,7 @@ -(void) handleCallback:(NSDictionary *) message { } - (NSArray *)supportedEvents { - return @[afOnAttributionFailure,afOnAppOpenAttribution,afOnInstallConversionFailure, afOnInstallConversionDataLoaded]; + return @[afOnAttributionFailure,afOnAppOpenAttribution,afOnInstallConversionFailure, afOnInstallConversionDataLoaded, afOnDeepLinking]; } -(void) reportOnFailure:(NSString *)errorMessage type:(NSString*) type { diff --git a/package.json b/package.json index 46684a45..097a3961 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-appsflyer", - "version": "6.1.10", + "version": "6.1.20", "description": "React Native Appsflyer plugin", "main": "index.js", "scripts": {