1- const { withDangerousMod, withAppDelegate , WarningAggregator } = require ( '@expo/config-plugins' ) ;
1+ const { withAppDelegate , withDangerousMod, withXcodeProject , WarningAggregator } = require ( '@expo/config-plugins' ) ;
22const { mergeContents } = require ( '@expo/config-plugins/build/utils/generateCode' ) ;
3+ const { getAppDelegate } = require ( '@expo/config-plugins/build/ios/Paths' ) ;
34const fs = require ( 'fs' ) ;
45const 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+ } ;
0 commit comments