Skip to content

Commit 6717d1c

Browse files
authored
Merge pull request #642 from AppsFlyerSDK/releases/6.x.x/6.17.x/6.17.2-rc1
Releases/6.x.x/6.17.x/6.17.2 rc1
2 parents 26b5f9b + 742a7be commit 6717d1c

10 files changed

Lines changed: 261 additions & 76 deletions

File tree

Docs/RN_ExpoInstallation.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ expo install react-native-appsflyer
2222
...
2323
"plugins": [
2424
[
25-
"react-native-appsflyer",{}
25+
"react-native-appsflyer",
26+
{
27+
"shouldUseStrictMode": false, // optional – kids-apps strict mode
28+
"shouldUsePurchaseConnector": true // NEW – enables Purchase Connector
29+
}
2630
]
2731
],
2832
...
@@ -112,3 +116,10 @@ In v6.8.0 of the AppsFlyer SDK, we added the normal permission com.google.androi
112116
to allow the SDK to collect the Android Advertising ID on apps targeting API 33.
113117
If your app is targeting children, you need to revoke this permission to comply with Google's Data policy.
114118
You can read more about it [here](https://docs.expo.dev/guides/permissions/#android).
119+
120+
### Purchase Connector (optional)
121+
122+
Setting `"shouldUsePurchaseConnector": true` will:
123+
124+
* **iOS** – add the `PurchaseConnector` CocoaPod automatically
125+
* **Android** – add `appsflyer.enable_purchase_connector=true` to `gradle.properties`

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
1313
### <a id="plugin-build-for"> This plugin is built for
1414

15-
- Android AppsFlyer SDK **v6.17.0**
16-
- iOS AppsFlyer SDK **v6.17.1**
15+
- Android AppsFlyer SDK **v6.17.1**
16+
- iOS AppsFlyer SDK **v6.17.2**
1717
- Tested with React-Native **v0.62.0** (older versions might be supported)
1818

1919
## <a id="release-updates"> Release Updates

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ repositories {
7070
dependencies {
7171
implementation "org.jetbrains.kotlin:kotlin-stdlib:1.7.10" // Add Kotlin standard library
7272
implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
73-
api "com.appsflyer:af-android-sdk:${safeExtGet('appsflyerVersion', '6.17.0')}"
73+
api "com.appsflyer:af-android-sdk:${safeExtGet('appsflyerVersion', '6.17.1')}"
7474
implementation "com.android.installreferrer:installreferrer:${safeExtGet('installReferrerVersion', '2.2')}"
7575
if (includeConnector){
7676
implementation 'com.appsflyer:purchase-connector:2.1.1'

android/src/main/java/com/appsflyer/reactnative/RNAppsFlyerConstants.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
public class RNAppsFlyerConstants {
88

9-
final static String PLUGIN_VERSION = "6.17.1";
9+
final static String PLUGIN_VERSION = "6.17.2";
1010
final static String NO_DEVKEY_FOUND = "No 'devKey' found or its empty";
1111
final static String UNKNOWN_ERROR = "AF Unknown Error";
1212
final static String SUCCESS = "Success";

expo/withAppsFlyer.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
const withAppsFlyerIos = require('./withAppsFlyerIos');
2-
module.exports = function withAppsFlyer(config, { shouldUseStrictMode = false } = {}) {
3-
config = withAppsFlyerIos(config, shouldUseStrictMode);
4-
return config;
2+
const withAppsFlyerAndroid = require('./withAppsFlyerAndroid');
3+
console.log('[AppsFlyerPlugin] Main plugin loaded');
4+
5+
module.exports = function withAppsFlyer(config, {
6+
shouldUseStrictMode = false,
7+
shouldUsePurchaseConnector = false
8+
} = {}) {
9+
config = withAppsFlyerIos(config, { shouldUseStrictMode, shouldUsePurchaseConnector });
10+
config = withAppsFlyerAndroid(config, { shouldUsePurchaseConnector });
11+
return config;
512
};

expo/withAppsFlyerAndroid.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
const { withGradleProperties } = require('@expo/config-plugins');
2+
3+
function addPurchaseConnectorFlag(config) {
4+
return withGradleProperties(config, (cfg) => {
5+
const props = cfg.modResults ?? [];
6+
const exists = props.some((p) => p.key === 'appsflyer.enable_purchase_connector');
7+
if (!exists) {
8+
props.push({ type: 'property', key: 'appsflyer.enable_purchase_connector', value: 'true' });
9+
} else {
10+
console.log('[AppsFlyerPlugin] Flag already present, no changes made');
11+
}
12+
return cfg;
13+
});
14+
}
15+
16+
module.exports = function withAppsFlyerAndroid(config, { shouldUsePurchaseConnector = false } = {}) {
17+
if (shouldUsePurchaseConnector) {
18+
config = addPurchaseConnectorFlag(config);
19+
} else {
20+
console.log('[AppsFlyerPlugin] Purchase Connector disabled, skipping gradle property injection');
21+
}
22+
return config;
23+
};

expo/withAppsFlyerIos.js

Lines changed: 207 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,218 @@
1-
const { withDangerousMod, withAppDelegate, WarningAggregator } = require('@expo/config-plugins');
1+
const { withAppDelegate, withDangerousMod, withXcodeProject, WarningAggregator } = require('@expo/config-plugins');
22
const { mergeContents } = require('@expo/config-plugins/build/utils/generateCode');
3+
const { getAppDelegate } = require('@expo/config-plugins/build/ios/Paths');
34
const fs = require('fs');
45
const path = require('path');
56

6-
const RNAPPSFLYER_IMPORT = `#import <RNAppsFlyer.h>\n`;
7-
const RNAPPSFLYER_CONTINUE_USER_ACTIVITY_IDENTIFIER = `- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {`;
8-
const RNAPPSFLYER_OPENURL_IDENTIFIER = `- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {`;
9-
const RNAPPSFLYER_CONTINUE_USER_ACTIVITY_CODE = `[[AppsFlyerAttribution shared] continueUserActivity:userActivity restorationHandler:restorationHandler];\n`;
10-
const RNAPPSFLYER_OPENURL_CODE = `[[AppsFlyerAttribution shared] handleOpenUrl:url options:options];\n`;
11-
12-
function modifyAppDelegate(appDelegate) {
13-
if (!appDelegate.includes(RNAPPSFLYER_IMPORT)) {
14-
appDelegate = RNAPPSFLYER_IMPORT + appDelegate;
15-
}
16-
if (appDelegate.includes(RNAPPSFLYER_CONTINUE_USER_ACTIVITY_IDENTIFIER) && !appDelegate.includes(RNAPPSFLYER_CONTINUE_USER_ACTIVITY_CODE)) {
17-
const block = RNAPPSFLYER_CONTINUE_USER_ACTIVITY_IDENTIFIER + '\n' + RNAPPSFLYER_CONTINUE_USER_ACTIVITY_CODE;
18-
appDelegate = appDelegate.replace(RNAPPSFLYER_CONTINUE_USER_ACTIVITY_IDENTIFIER, block);
19-
} else {
20-
WarningAggregator.addWarningIOS('withAppsFlyerAppDelegate', "Failed to detect continueUserActivity in AppDelegate or AppsFlyer's delegate method already exists");
21-
}
22-
if (appDelegate.includes(RNAPPSFLYER_OPENURL_IDENTIFIER) && !appDelegate.includes(RNAPPSFLYER_OPENURL_CODE)) {
23-
const block = RNAPPSFLYER_OPENURL_IDENTIFIER + '\n' + RNAPPSFLYER_OPENURL_CODE;
24-
appDelegate = appDelegate.replace(RNAPPSFLYER_OPENURL_IDENTIFIER, block);
25-
} else {
26-
WarningAggregator.addWarningIOS('withAppsFlyerAppDelegate', "Failed to detect openURL in AppDelegate or AppsFlyer's delegate method already exists");
27-
}
28-
return appDelegate;
7+
function getBridgingHeaderPathFromXcode(project) {
8+
const buildConfigs = project.pbxXCBuildConfigurationSection();
9+
10+
for (const key in buildConfigs) {
11+
const config = buildConfigs[key];
12+
if (
13+
typeof config === 'object' &&
14+
config.buildSettings &&
15+
config.buildSettings['SWIFT_OBJC_BRIDGING_HEADER']
16+
) {
17+
const bridgingHeaderPath = config.buildSettings[
18+
'SWIFT_OBJC_BRIDGING_HEADER'
19+
].replace(/"/g, '');
20+
21+
return bridgingHeaderPath;
22+
}
23+
}
24+
25+
return null;
2926
}
3027

31-
function withAppsFlyerAppDelegate(config) {
32-
return withAppDelegate(config, (config) => {
33-
if (['objc', 'objcpp'].includes(config.modResults.language)) {
34-
config.modResults.contents = modifyAppDelegate(config.modResults.contents);
35-
} else {
36-
WarningAggregator.addWarningIOS('withAppsFlyerAppDelegate', `${config.modResults.language} AppDelegate file is not supported yet`);
37-
}
38-
return config;
39-
});
28+
function modifyObjcAppDelegate(appDelegate) {
29+
const RNAPPSFLYER_IMPORT = `#import <RNAppsFlyer.h>\n`;
30+
const RNAPPSFLYER_CONTINUE_USER_ACTIVITY_IDENTIFIER = `- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {`;
31+
const RNAPPSFLYER_OPENURL_IDENTIFIER = `- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {`;
32+
const RNAPPSFLYER_CONTINUE_USER_ACTIVITY_CODE = `[[AppsFlyerAttribution shared] continueUserActivity:userActivity restorationHandler:restorationHandler];\n`;
33+
const RNAPPSFLYER_OPENURL_CODE = `[[AppsFlyerAttribution shared] handleOpenUrl:url options:options];\n`;
34+
35+
if (!appDelegate.includes(RNAPPSFLYER_IMPORT)) {
36+
appDelegate = RNAPPSFLYER_IMPORT + appDelegate;
37+
}
38+
if (appDelegate.includes(RNAPPSFLYER_CONTINUE_USER_ACTIVITY_IDENTIFIER) && !appDelegate.includes(RNAPPSFLYER_CONTINUE_USER_ACTIVITY_CODE)) {
39+
const block = RNAPPSFLYER_CONTINUE_USER_ACTIVITY_IDENTIFIER + '\n' + RNAPPSFLYER_CONTINUE_USER_ACTIVITY_CODE;
40+
appDelegate = appDelegate.replace(RNAPPSFLYER_CONTINUE_USER_ACTIVITY_IDENTIFIER, block);
41+
} else {
42+
WarningAggregator.addWarningIOS('withAppsFlyerAppDelegate', "Failed to detect continueUserActivity in AppDelegate or AppsFlyer's delegate method already exists");
43+
}
44+
if (appDelegate.includes(RNAPPSFLYER_OPENURL_IDENTIFIER) && !appDelegate.includes(RNAPPSFLYER_OPENURL_CODE)) {
45+
const block = RNAPPSFLYER_OPENURL_IDENTIFIER + '\n' + RNAPPSFLYER_OPENURL_CODE;
46+
appDelegate = appDelegate.replace(RNAPPSFLYER_OPENURL_IDENTIFIER, block);
47+
} else {
48+
WarningAggregator.addWarningIOS('withAppsFlyerAppDelegate', "Failed to detect openURL in AppDelegate or AppsFlyer's delegate method already exists");
49+
}
50+
return appDelegate;
51+
}
52+
53+
function modifySwiftAppDelegate(appDelegateContents) {
54+
const SWIFT_OPENURL_IDENTIFIER = ` public override func application(
55+
_ app: UIApplication,
56+
open url: URL,
57+
options: [UIApplication.OpenURLOptionsKey: Any] = [:]
58+
) -> Bool {`;
59+
const RNAPPSFLYER_SWIFT_OPENURL_CODE = 'AppsFlyerAttribution.shared().handleOpen(url, options: options)';
60+
61+
const SWIFT_CONTINUE_USER_ACTIVITY_IDENTIFIER = ` public override func application(
62+
_ application: UIApplication,
63+
continue userActivity: NSUserActivity,
64+
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
65+
) -> Bool {`;
66+
const RNAPPSFLYER_SWIFT_CONTINUE_USER_ACTIVITY_CODE = 'AppsFlyerAttribution.shared().continue(userActivity, restorationHandler: nil)';
67+
68+
if (appDelegateContents.includes(SWIFT_OPENURL_IDENTIFIER) && !appDelegateContents.includes(RNAPPSFLYER_SWIFT_OPENURL_CODE)) {
69+
appDelegateContents = appDelegateContents.replace(SWIFT_OPENURL_IDENTIFIER, `${SWIFT_OPENURL_IDENTIFIER}\n ${RNAPPSFLYER_SWIFT_OPENURL_CODE}`);
70+
}
71+
72+
if (appDelegateContents.includes(SWIFT_CONTINUE_USER_ACTIVITY_IDENTIFIER) && !appDelegateContents.includes(RNAPPSFLYER_SWIFT_CONTINUE_USER_ACTIVITY_CODE)) {
73+
appDelegateContents = appDelegateContents.replace(SWIFT_CONTINUE_USER_ACTIVITY_IDENTIFIER, `${SWIFT_CONTINUE_USER_ACTIVITY_IDENTIFIER}\n ${RNAPPSFLYER_SWIFT_CONTINUE_USER_ACTIVITY_CODE}`);
74+
}
75+
76+
if (!appDelegateContents.includes(RNAPPSFLYER_SWIFT_OPENURL_CODE) || !appDelegateContents.includes(RNAPPSFLYER_SWIFT_CONTINUE_USER_ACTIVITY_CODE)) {
77+
WarningAggregator.addWarningIOS(
78+
'withAppsFlyerAppDelegate',
79+
`
80+
Automatic Swift AppDelegate modification failed.
81+
Please add AppsFlyer integration manually:
82+
83+
1. Add this to your openURL method:
84+
AppsFlyerAttribution.shared().handleOpen(url, options: options)
85+
86+
2. Add this to your continueUserActivity method:
87+
AppsFlyerAttribution.shared().continue(userActivity, restorationHandler: nil)
88+
89+
Supported format: Expo SDK default template
90+
`
91+
);
92+
}
93+
94+
return appDelegateContents;
4095
}
4196

42-
function withPodfile(config, shouldUseStrictMode) {
43-
return withDangerousMod(config, [
44-
'ios',
45-
async (config) => {
46-
const filePath = path.join(config.modRequest.platformProjectRoot, 'Podfile');
47-
const contents = fs.readFileSync(filePath, 'utf-8');
48-
49-
const mergedPodfileWithStrictMode = mergeContents({
50-
tag: 'AppsFlyer Strict Mode',
51-
src: contents,
52-
newSrc: `$RNAppsFlyerStrictMode=${shouldUseStrictMode}`,
53-
anchor: 'use_expo_modules!',
54-
offset: 0,
55-
comment: '#',
56-
});
57-
58-
if (!mergedPodfileWithStrictMode.didMerge) {
59-
console.log("ERROR: Cannot add AppsFlyer strict mode to the project's ios/Podfile because it's malformed. Please report this with a copy of your project Podfile.");
60-
return config;
61-
}
62-
63-
fs.writeFileSync(filePath, mergedPodfileWithStrictMode.contents);
64-
65-
return config;
66-
},
67-
]);
97+
function withAppsFlyerAppDelegate(config) {
98+
return withAppDelegate(config, (config) => {
99+
const language = config.modResults.language;
100+
101+
if (['objc', 'objcpp'].includes(language)) {
102+
config.modResults.contents = modifyObjcAppDelegate(config.modResults.contents);
103+
} else if (language === 'swift') {
104+
config.modResults.contents = modifySwiftAppDelegate(config.modResults.contents);
105+
} else {
106+
WarningAggregator.addWarningIOS('withAppsFlyerAppDelegate', `${language} AppDelegate file is not supported yet`);
107+
}
108+
return config;
109+
});
68110
}
69111

70-
module.exports = function withAppsFlyerIos(config, shouldUseStrictMode) {
71-
config = withPodfile(config, shouldUseStrictMode);
72-
config = withAppsFlyerAppDelegate(config);
73-
return config;
112+
const withIosBridgingHeader = (config) => {
113+
return withXcodeProject(config, (action) => {
114+
const projectRoot = action.modRequest.projectRoot;
115+
const appDelegate = getAppDelegate(projectRoot);
116+
117+
if (appDelegate.language === 'swift') {
118+
const bridgingHeaderPath = getBridgingHeaderPathFromXcode(
119+
action.modResults,
120+
);
121+
122+
const bridgingHeaderFilePath = path.join(
123+
action.modRequest.platformProjectRoot,
124+
bridgingHeaderPath,
125+
);
126+
127+
if (fs.existsSync(bridgingHeaderFilePath)) {
128+
let content = fs.readFileSync(bridgingHeaderFilePath, 'utf8');
129+
const appsFlyerImport = '#import <RNAppsFlyer.h>';
130+
131+
if (!content.includes(appsFlyerImport)) {
132+
content += `${appsFlyerImport}\n`;
133+
fs.writeFileSync(bridgingHeaderFilePath, content);
134+
}
135+
136+
return action;
137+
}
138+
139+
WarningAggregator.addWarningIOS(
140+
'withIosBridgingHeader',
141+
`
142+
Failed to detect ${bridgingHeaderPath} file. Please add AppsFlyer integration manually:
143+
#import <RNAppsFlyer.h>
144+
145+
Supported format: Expo SDK default template
146+
`
147+
);
148+
149+
return action;
150+
}
151+
152+
return action;
153+
});
74154
};
155+
156+
function withPodfile(config, shouldUseStrictMode, shouldUsePurchaseConnector) {
157+
return withDangerousMod(config, [
158+
'ios',
159+
async (config) => {
160+
const filePath = path.join(config.modRequest.platformProjectRoot, 'Podfile');
161+
const contents = fs.readFileSync(filePath, 'utf-8');
162+
163+
let mergedContents = { contents, didMerge: true };
164+
165+
// Check if Strict Mode flag already exists
166+
if (!contents.includes('$RNAppsFlyerStrictMode')) {
167+
mergedContents = mergeContents({
168+
tag: 'AppsFlyer Strict Mode',
169+
src: mergedContents.contents,
170+
newSrc: `$RNAppsFlyerStrictMode=${shouldUseStrictMode}`,
171+
anchor: 'use_expo_modules!',
172+
offset: 0,
173+
comment: '#',
174+
});
175+
176+
if (!mergedContents.didMerge) {
177+
console.log("ERROR: Cannot add AppsFlyer strict mode to the project's ios/Podfile because it's malformed. Please report this with a copy of your project Podfile.");
178+
return config;
179+
}
180+
} else {
181+
console.log("INFO: $RNAppsFlyerStrictMode already exists in Podfile, skipping auto-assignment.");
182+
}
183+
184+
// Check if Purchase Connector flag already exists
185+
if (!contents.includes('$AppsFlyerPurchaseConnector')) {
186+
mergedContents = mergeContents({
187+
tag: 'AppsFlyer Purchase Connector',
188+
src: mergedContents.contents,
189+
newSrc: `$AppsFlyerPurchaseConnector=${shouldUsePurchaseConnector}`,
190+
anchor: 'use_expo_modules!',
191+
offset: mergedContents.contents.includes('$RNAppsFlyerStrictMode') ? 1 : 0,
192+
comment: '#',
193+
});
194+
195+
if (!mergedContents.didMerge) {
196+
console.log("ERROR: Cannot add AppsFlyer Purchase Connector to the project's ios/Podfile because it's malformed. Please report this with a copy of your project Podfile.");
197+
return config;
198+
}
199+
} else {
200+
console.log("INFO: $AppsFlyerPurchaseConnector already exists in Podfile, skipping auto-assignment.");
201+
}
202+
203+
fs.writeFileSync(filePath, mergedContents.contents);
204+
205+
return config;
206+
},
207+
]);
208+
}
209+
210+
module.exports = function withAppsFlyerIos(config, {
211+
shouldUseStrictMode = false,
212+
shouldUsePurchaseConnector = false
213+
} = {}) {
214+
config = withPodfile(config, shouldUseStrictMode, shouldUsePurchaseConnector);
215+
config = withIosBridgingHeader(config);
216+
config = withAppsFlyerAppDelegate(config);
217+
return config;
218+
};

ios/RNAppsFlyer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
@end
2323

2424

25-
static NSString *const kAppsFlyerPluginVersion = @"6.17.1";
25+
static NSString *const kAppsFlyerPluginVersion = @"6.17.2";
2626
static NSString *const NO_DEVKEY_FOUND = @"No 'devKey' found or its empty";
2727
static NSString *const NO_APPID_FOUND = @"No 'appId' found or its empty";
2828
static NSString *const NO_EVENT_NAME_FOUND = @"No 'eventName' found or its empty";

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-appsflyer",
3-
"version": "6.17.1",
3+
"version": "6.17.2-rc1",
44
"description": "React Native Appsflyer plugin",
55
"main": "index.js",
66
"types": "index.d.ts",

0 commit comments

Comments
 (0)