diff --git a/.github/workflows/scripts/releaseNotesGenerator.sh b/.github/workflows/scripts/releaseNotesGenerator.sh index 51419370..2c0f5897 100755 --- a/.github/workflows/scripts/releaseNotesGenerator.sh +++ b/.github/workflows/scripts/releaseNotesGenerator.sh @@ -2,7 +2,7 @@ JIRA_TOKEN=$1 JIRA_FIXED_VERSION=$2 fixed_version_found=false -curl -X GET -H "Authorization: Basic $JIRA_TOKEN=" https://appsflyer.atlassian.net/rest/api/3/project/11400/versions | jq -r '.[] | .name+""+.id' | while read version ; do +curl -X GET -H "Authorization: Basic $JIRA_TOKEN=" https://appsflyer.atlassian.net/rest/api/3/project/11723/versions | jq -r '.[] | .name+""+.id' | while read version ; do if [[ "$version" == *"$JIRA_FIXED_VERSION"* ]] ;then echo "$JIRA_FIXED_VERSION Found!" fixed_version_found=true diff --git a/.gitignore b/.gitignore index 53d78edd..8cfdc82a 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ local.properties android/gradlew.bat .vscode android/gradle +android/gradlew android/.project # node.js @@ -50,7 +51,7 @@ buck-out/ android/app/libs android/keystores/debug.keystore - +.watchmanconfig coverage/ package-lock.json diff --git a/CHANGELOG.md b/CHANGELOG.md index c751e6fd..c4625529 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 6.9.1 + Release date: *2022-09-22* + +- React Native >> Support Deep Linking without calling startSDK() +- React Native >> Separate between initSDK and startSDK + ## 6.8.2 Release date: *2022-08-30* diff --git a/Docs/API.md b/Docs/API.md index 6c9ce367..64724432 100644 --- a/Docs/API.md +++ b/Docs/API.md @@ -4,6 +4,7 @@ The list of available methods for this plugin is described below. - [Android & iOS APIs](#allAPI) - [initSDK](#initSDK) + - [startSdk](#startSdk) - [logEvent](#logEvent) - [setCustomerUserId](#setCustomerUserId) - [stop](#stop) @@ -30,6 +31,7 @@ The list of available methods for this plugin is described below. - [setCollectAndroidID](#setCollectAndroidID) - [setCollectIMEI](#setCollectIMEI) - [setDisableNetworkData](#setDisableNetworkData) + - [performOnDeepLinking](#performOnDeepLinking) - [iOS Only APIs](#iOSOnly) - [disableCollectASA](#disableCollectASA) - [setUseReceiptValidationSandbox](#setUseReceiptValidationSandbox) @@ -64,6 +66,7 @@ The dev key is required for all apps and the appID is required only for iOS.
{ console.log(res); @@ -88,6 +92,44 @@ appsFlyer.initSdk( } ); ``` + +--- + +##### **`startSdk()`** + +In version 6.9.1 of the react-native-appslfyer SDK we added the option of splitting between the initialization stage and start stage. All you need to do is add the property manualStart: true to the init object, and later call appsFlyer.startSdk() whenever you decide. If this property is set to false or doesn’t exist, the sdk will start after calling `appsFlyer.initSdk(...)`. + +*Example:* +```javascript +const option = { + isDebug: true, + devKey: 'UsxXxXxed', + appId: '75xXxXxXxXx11', + onInstallConversionDataListener: true, + onDeepLinkListener: true, + timeToWaitForATTUserAuthorization: 5, + manualStart: true, // <--- for manual start. +}; + +appsFlyer.initSdk( + option, + () => { + if (!option.manualStart) { + console.warn('AppsFlyer SDK started!'); + } else { + console.warn('AppsFlyer SDK init, didn\'t send launch yet'); + } + }, + err => { + // handle error + }, + ); + //... + // app flow + //... + + appsFlyer.startSdk(); // <--- Here we send launch +``` --- ##### **`logEvent(eventName, eventValues, success, error)`** @@ -713,13 +755,57 @@ Use to opt-out of collecting the network operator name (carrier) and sim operato *Example:* - ```javascript if (Platform.OS == 'android') { appsFlyer.setDisableNetworkData(true); } ``` +##### **`performOnDeepLinking()`** + +Enables manual triggering of deep link resolution. This method allows apps that are delaying the call to `appsFlyer.startSdk()` to resolve deep links before the SDK starts.
+Note:
This API will trigger the `appsFlyer.onDeepLink` callback. In the following example, we check if `res.deepLinkStatus` is equal to “FOUND” inside `appsFlyer.onDeepLink` callback to extract the deeplink parameters. + +*Example:* +```javascript +// Let's say we want the resolve a deeplink and get the deeplink params when the user clicks on it but delay the actual 'start' of the sdk (not sending launch to appsflyer). + +const option = { + isDebug: true, + devKey: 'UsxXxXxed', + appId: '75xXxXxXxXx11', + onInstallConversionDataListener: true, + onDeepLinkListener: true, + manualStart: true, // <--- for manual start. +}; + +const onDeepLink = appsFlyer.onDeepLink(res => { + if (res.deepLinkStatus == 'FOUND') { + // here we will get the deeplink params after resolving it. + // more flow... + } +}); + +appsFlyer.initSdk( + option, + () => { + if (!option.manualStart) { + console.warn('AppsFlyer SDK started!'); + } else { + console.warn('AppsFlyer SDK init, didn\'t send launch yet'); + } + }, + () => {}, +); + +if (Platform.OS == 'android') { + appsFlyer.performOnDeepLinking(); +} + +// more app flow... + +appsFlyer.startSdk(); // <--- Here we send launch +``` ##
iOS Only APIs ##### **`disableCollectASA(shouldDisable)`** diff --git a/README.md b/README.md index 909073ac..e2d90ed1 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ ### This plugin is built for -- Android AppsFlyer SDK **v6.8.2** +- Android AppsFlyer SDK **v6.9.1** - iOS AppsFlyer SDK **v6.8.1** ## ❗❗ Breaking changes when updating to v6.x.x❗❗ diff --git a/__tests__/index.test.js b/__tests__/index.test.js index c0f07d99..1d1553b6 100644 --- a/__tests__/index.test.js +++ b/__tests__/index.test.js @@ -205,6 +205,36 @@ describe("Test appsFlyer API's", () => { appsFlyer.sendPushNotificationData({ foo: 'bar' }); expect(RNAppsFlyer.sendPushNotificationData).toHaveBeenCalledTimes(1); }); + + test('it calls appsFlyer.appendParametersToDeepLinkingURL(dummy-url, foo)', () => { + appsFlyer.appendParametersToDeepLinkingURL('dummy-url', 'foo'); + expect(RNAppsFlyer.appendParametersToDeepLinkingURL).toHaveBeenCalledTimes(0); + }); + + test('it calls appsFlyer.appendParametersToDeepLinkingURL(dummy-url, boolean)', () => { + appsFlyer.appendParametersToDeepLinkingURL('dummy-url', true); + expect(RNAppsFlyer.appendParametersToDeepLinkingURL).toHaveBeenCalledTimes(0); + }); + + test('it calls appsFlyer.appendParametersToDeepLinkingURL(dummy-url, {})', () => { + appsFlyer.appendParametersToDeepLinkingURL('dummy-url', {}); + expect(RNAppsFlyer.appendParametersToDeepLinkingURL).toHaveBeenCalledTimes(1); + }); + + test('it calls appsFlyer.setDisableNetworkData(true)', () => { + appsFlyer.setDisableNetworkData(true); + expect(RNAppsFlyer.setDisableNetworkData).toHaveBeenCalledTimes(1); + }); + + test('it calls appsFlyer.startSdk()', () => { + appsFlyer.startSdk(); + expect(RNAppsFlyer.startSdk).toHaveBeenCalledTimes(1); + }); + + test('it calls appsFlyer.performOnDeepLinking()', () => { + appsFlyer.performOnDeepLinking(); + expect(RNAppsFlyer.performOnDeepLinking).toHaveBeenCalledTimes(1); + }); }); describe('Test native event emitter', () => { diff --git a/__tests__/setup.js b/__tests__/setup.js index e6bca2ed..2f18beb7 100644 --- a/__tests__/setup.js +++ b/__tests__/setup.js @@ -18,6 +18,10 @@ jest.mock('../node_modules/react-native/Libraries/BatchedBridge/NativeModules', setSharingFilterForPartners: jest.fn(), setCurrentDeviceLanguage: jest.fn(), sendPushNotificationData: jest.fn(), + appendParametersToDeepLinkingURL: jest.fn(), + setDisableNetworkData: jest.fn(), + performOnDeepLinking: jest.fn(), + startSdk: jest.fn(), }, }; }); diff --git a/android/build.gradle b/android/build.gradle index 519d65c1..fe4a34e7 100755 --- a/android/build.gradle +++ b/android/build.gradle @@ -54,5 +54,5 @@ repositories { dependencies { implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}" implementation "com.android.installreferrer:installreferrer:${safeExtGet('installReferrerVersion', '2.1')}" - implementation "com.appsflyer:af-android-sdk:${safeExtGet('appsflyerVersion', '6.8.2')}" + implementation "com.appsflyer:af-android-sdk:${safeExtGet('appsflyerVersion', '6.9.1')}" } diff --git a/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java b/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java index b670d380..82264b99 100755 --- a/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java +++ b/android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerModule.java @@ -50,11 +50,13 @@ public class RNAppsFlyerModule extends ReactContextBaseJavaModule { private ReactApplicationContext reactContext; private Application application; + private String personalDevKey; public RNAppsFlyerModule(ReactApplicationContext reactContext) { super(reactContext); this.reactContext = reactContext; this.application = (Application) reactContext.getApplicationContext(); + this.personalDevKey = ""; } @Override @@ -128,6 +130,7 @@ private String callSdkInternal(ReadableMap _options) { boolean isDebug; boolean isConversionData; boolean isDeepLinking; + boolean isManualStartMode; AppsFlyerLib instance = AppsFlyerLib.getInstance(); JSONObject options = RNUtil.readableMapToJson(_options); @@ -135,6 +138,8 @@ private String callSdkInternal(ReadableMap _options) { if (devKey.trim().equals("")) { return NO_DEVKEY_FOUND; } + this.personalDevKey = devKey; + isDebug = options.optBoolean(afIsDebug, false); instance.setDebugLog(isDebug); @@ -143,22 +148,15 @@ private String callSdkInternal(ReadableMap _options) { Log.d("AppsFlyer", "Starting SDK"); } isDeepLinking = options.optBoolean(afDeepLink, false); + isManualStartMode = options.optBoolean("manualStart", false); instance.init(devKey, (isConversionData == true) ? registerConversionListener() : null, application.getApplicationContext()); if (isDeepLinking) { instance.subscribeForDeepLink(registerDeepLinkListener()); } - Intent intent = null; - Activity currentActivity = getCurrentActivity(); - if (currentActivity != null) { - // register for lifecycle with Activity (automatically fetching deeplink from Activity if present) - instance.start(currentActivity, devKey); - } else { - // register for lifecycle with Application (cannot fetch deeplink without access to the Activity, - // also sending first session manually) - instance.logEvent(application, null, null); - instance.start(application, devKey); + if (!isManualStartMode) { + startSdk(); } return null; } @@ -262,6 +260,20 @@ private void sendEvent(ReactContext reactContext, .emit(eventName, params); } + @ReactMethod + public void startSdk() { + Activity currentActivity = getCurrentActivity(); + if (currentActivity != null) { + // register for lifecycle with Activity (automatically fetching deeplink from Activity if present) + AppsFlyerLib.getInstance().start(currentActivity, this.personalDevKey); + } else { + // register for lifecycle with Application (cannot fetch deeplink without access to the Activity, + // also sending first session manually) + AppsFlyerLib.getInstance().logEvent(this.application, null, null); + AppsFlyerLib.getInstance().start(this.application, this.personalDevKey); + } + } + @ReactMethod public void logEvent( final String eventName, ReadableMap eventData, @@ -766,6 +778,21 @@ public void appendParametersToDeepLinkingURL(String contains, ReadableMap parame public void setDisableNetworkData(Boolean disabled) { AppsFlyerLib.getInstance().setDisableNetworkData(disabled); } + + @ReactMethod + public void performOnDeepLinking() { + Activity activity = getCurrentActivity(); + if (activity != null) { + Intent intent = activity.getIntent(); + if (intent != null) { + AppsFlyerLib.getInstance().performOnDeepLinking(intent, this.application); + } else { + Log.d("AppsFlyer", "performOnDeepLinking: intent is null!"); + } + } else{ + Log.d("AppsFlyer", "performOnDeepLinking: activity is null!"); + } + } @ReactMethod public void addListener(String eventName) { diff --git a/index.d.ts b/index.d.ts index ec14a6f1..3e2a03f2 100644 --- a/index.d.ts +++ b/index.d.ts @@ -79,6 +79,7 @@ declare module "react-native-appsflyer" { onInstallConversionDataListener?: boolean; onDeepLinkListener?: boolean; timeToWaitForATTUserAuthorization?: number; // iOS only + manualStart?: boolean } export interface InAppPurchase { @@ -142,6 +143,7 @@ declare module "react-native-appsflyer" { setSharingFilterForPartners(partners: string[]): void setPartnerData(partnerId: string, partnerData: object): void appendParametersToDeepLinkingURL(contains: string, parameters: object): void + startSdk(): void /** * For iOS Only @@ -157,6 +159,7 @@ declare module "react-native-appsflyer" { setCollectIMEI(isCollect: boolean, successC?: SuccessCB): void setCollectAndroidID(isCollect: boolean, successC?: SuccessCB): void setDisableNetworkData(disable: boolean): void + performOnDeepLinking(): void }; export default appsFlyer; diff --git a/index.js b/index.js index a8cbb8b3..f0e703f2 100755 --- a/index.js +++ b/index.js @@ -608,6 +608,14 @@ appsFlyer.setDisableNetworkData = (disable) => { return RNAppsFlyer.setDisableNetworkData(disable); }; +appsFlyer.startSdk = () => { + return RNAppsFlyer.startSdk(); +}; + +appsFlyer.performOnDeepLinking = () => { + return RNAppsFlyer.performOnDeepLinking(); +}; + function AFParseJSONException(_message, _data) { this.message = _message; this.data = _data; diff --git a/ios/RNAppsFlyer.h b/ios/RNAppsFlyer.h index 919a096a..143c38bd 100755 --- a/ios/RNAppsFlyer.h +++ b/ios/RNAppsFlyer.h @@ -18,6 +18,7 @@ @interface RNAppsFlyer: RCTEventEmitter +@property (readwrite, nonatomic) BOOL isManualStart; @end diff --git a/ios/RNAppsFlyer.m b/ios/RNAppsFlyer.m index b9c6376b..b25c3d14 100755 --- a/ios/RNAppsFlyer.m +++ b/ios/RNAppsFlyer.m @@ -38,6 +38,7 @@ -(NSError *) callSdkInternal:(NSDictionary*)initSdkOptions { BOOL isDebug = NO; BOOL isConversionData = YES; BOOL isDeepLinking = NO; + BOOL isManualStart = NO; NSNumber* interval = 0; if (![initSdkOptions isKindOfClass:[NSNull class]]) { @@ -48,6 +49,8 @@ -(NSError *) callSdkInternal:(NSDictionary*)initSdkOptions { devKey = (NSString*)[initSdkOptions objectForKey: afDevKey]; appId = (NSString*)[initSdkOptions objectForKey: afAppId]; interval = (NSNumber*)[initSdkOptions objectForKey: timeToWaitForATTUserAuthorization]; + isManualStart = [[initSdkOptions objectForKey:@"manualStart"] boolValue]; + [self setIsManualStart:isManualStart]; isDebugValue = [initSdkOptions objectForKey: afIsDebug]; if ([isDebugValue isKindOfClass:[NSNumber class]]) { @@ -103,11 +106,18 @@ -(NSError *) callSdkInternal:(NSDictionary*)initSdkOptions { selector:@selector(sendLaunch:) name:UIApplicationDidBecomeActiveNotification object:nil]; - [[AppsFlyerLib shared] start]; + if (!isManualStart) { + [[AppsFlyerLib shared] start]; + } return nil; } } +RCT_EXPORT_METHOD(startSdk) { + [self setIsManualStart:NO]; + [[AppsFlyerLib shared] start]; +} + RCT_EXPORT_METHOD(logEvent: (NSString *)eventName eventValues:(NSDictionary *)eventValues successCallback :(RCTResponseSenderBlock)successCallback errorCallback:(RCTResponseSenderBlock)errorCallback) { @@ -213,7 +223,9 @@ -(NSError *) callSdkInternal:(NSDictionary*)initSdkOptions { -(void)sendLaunch:(UIApplication *)application { [[NSNotificationCenter defaultCenter] postNotificationName:RNAFBridgeInitializedNotification object:self]; - [[AppsFlyerLib shared] start]; + if (![self isManualStart]) { + [[AppsFlyerLib shared] start]; + } } RCT_EXPORT_METHOD(setAdditionalData: (NSDictionary *)additionalData callback:(RCTResponseSenderBlock)callback) { diff --git a/package.json b/package.json index 98f23350..90355d29 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-appsflyer", - "version": "6.8.2", + "version": "6.9.1", "description": "React Native Appsflyer plugin", "main": "index.js", "types": "index.d.ts",