Skip to content

Commit f15b14d

Browse files
committed
Ported Expo plug-in from internal code; fixed naming issues
1 parent aaf456a commit f15b14d

File tree

12 files changed

+677
-3
lines changed

12 files changed

+677
-3
lines changed

packages/react-native-app-auth/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"jest": "^29.6.3",
4141
"react": "19.0.0",
4242
"react-native": "0.79.2",
43+
"@expo/config-plugins": "~10.1.1",
4344
"@react-native/babel-preset": "0.79.2"
4445
},
4546
"dependencies": {
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const fs = require('node:fs');
2+
3+
const { AndroidConfig, withDangerousMod } = require('@expo/config-plugins');
4+
const codeModAndroid = require('@expo/config-plugins/build/android/codeMod');
5+
6+
const withAppAuthAppBuildGradle = (rootConfig, props) =>
7+
withDangerousMod(rootConfig, [
8+
'android',
9+
config => {
10+
// find the app/build.gradle file and checks its format
11+
const appBuildGradlePath = AndroidConfig.Paths.getAppBuildGradleFilePath(
12+
config.modRequest.projectRoot,
13+
);
14+
15+
// BEWARE: we update the app/build.gradle file *outside* of the standard Expo config procedure !
16+
let contents = fs.readFileSync(appBuildGradlePath, 'utf8');
17+
18+
if (contents.includes('manifestPlaceholders')) {
19+
throw new Error(
20+
'app/build.gradle already contains manifestPlaceholders, cannot update automatically !',
21+
);
22+
}
23+
24+
contents = codeModAndroid.appendContentsInsideDeclarationBlock(
25+
contents,
26+
'defaultConfig',
27+
` manifestPlaceholders = [
28+
appAuthRedirectScheme: '${props?.android?.appAuthRedirectScheme}',
29+
]
30+
`,
31+
);
32+
33+
// and finally we write the file back to the disk
34+
fs.writeFileSync(appBuildGradlePath, contents, 'utf8');
35+
36+
return config;
37+
},
38+
]);
39+
40+
module.exports = { withAppAuthAppBuildGradle };
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const { withAppAuthAppBuildGradle } = require('./app-build-gradle');
2+
3+
module.exports = {
4+
withAppAuthAppBuildGradle,
5+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const EXPO_SDK_MAJOR_VERSION = 53;
2+
3+
const isExpo53OrLater = config => {
4+
const expoSdkVersion = config.sdkVersion || '0.0.0';
5+
const [major] = expoSdkVersion.split('.');
6+
return Number.parseInt(major, 10) >= EXPO_SDK_MAJOR_VERSION;
7+
};
8+
9+
module.exports = {
10+
isExpo53OrLater,
11+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const { withPlugins, createRunOncePlugin } = require('@expo/config-plugins');
2+
3+
const packageJson = require('../package.json');
4+
5+
const {
6+
withAppAuthAppDelegate,
7+
withAppAuthAppDelegateHeader,
8+
withUrlSchemes,
9+
withBridgingHeader,
10+
withXcodeBuildSettings,
11+
} = require('./ios');
12+
const { withAppAuthAppBuildGradle } = require('./android');
13+
14+
const withAppAuth = (config, props) => {
15+
return withPlugins(config, [
16+
// iOS
17+
withBridgingHeader,
18+
withXcodeBuildSettings,
19+
withAppAuthAppDelegate,
20+
withAppAuthAppDelegateHeader, // 👈 ️this one uses withDangerousMod !
21+
[withUrlSchemes, props],
22+
23+
// Android
24+
[withAppAuthAppBuildGradle, props], // 👈 ️this one uses withDangerousMod !
25+
]);
26+
};
27+
28+
module.exports = createRunOncePlugin(withAppAuth, packageJson.name, packageJson.version);
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
const fs = require('fs');
2+
3+
const { IOSConfig, withDangerousMod } = require('@expo/config-plugins');
4+
const codeModIOs = require('@expo/config-plugins/build/ios/codeMod');
5+
6+
const { isExpo53OrLater } = require('../expo-version');
7+
8+
const { insertProtocolDeclaration } = require('./utils/insert-protocol-declaration');
9+
10+
const withAppAuthAppDelegateHeader = rootConfig => {
11+
if (isExpo53OrLater(rootConfig)) {
12+
return rootConfig;
13+
}
14+
15+
return withDangerousMod(rootConfig, [
16+
'ios',
17+
config => {
18+
// find the AppDelegate.h file in the project
19+
const headerFilePath = IOSConfig.Paths.getAppDelegateObjcHeaderFilePath(
20+
config.modRequest.projectRoot
21+
);
22+
23+
// BEWARE: we update the AppDelegate.h file *outside* of the standard Expo config procedure !
24+
let contents = fs.readFileSync(headerFilePath, 'utf8');
25+
26+
const importExpoHeader = '#import <Expo/Expo.h>';
27+
const importRNAppAuthHeaders =
28+
'#import <React/RCTLinkingManager.h>\n#import "RNAppAuthAuthorizationFlowManager.h"';
29+
30+
contents = contents.replace(
31+
importExpoHeader,
32+
`${importExpoHeader}\n${importRNAppAuthHeaders}`
33+
);
34+
35+
// adds a new protocol to the AppDelegate interface (unless it already exists)
36+
contents = insertProtocolDeclaration({
37+
source: contents,
38+
interfaceName: 'AppDelegate',
39+
protocolName: 'RNAppAuthAuthorizationFlowManager',
40+
baseClassName: 'EXAppDelegateWrapper',
41+
});
42+
43+
contents = codeModIOs.insertContentsInsideObjcInterfaceBlock(
44+
contents,
45+
'@interface AppDelegate',
46+
`\n
47+
@property(nonatomic, weak) id<RNAppAuthAuthorizationFlowManagerDelegate> authorizationFlowManagerDelegate;`,
48+
{ position: 'head' }
49+
);
50+
51+
// and finally we write the file back to the disk
52+
fs.writeFileSync(headerFilePath, contents, 'utf8');
53+
54+
return config;
55+
},
56+
]);
57+
};
58+
59+
module.exports = {
60+
withAppAuthAppDelegateHeader,
61+
};
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
const { withAppDelegate } = require('@expo/config-plugins');
2+
const codeModIOs = require('@expo/config-plugins/build/ios/codeMod');
3+
4+
const { isExpo53OrLater } = require('../expo-version');
5+
6+
const withAppDelegateSwift = rootConfig => {
7+
return withAppDelegate(rootConfig, config => {
8+
let { contents } = config.modResults;
9+
10+
if (!contents.includes('RNAppAuthAuthorizationFlowManager')) {
11+
const replaceText = 'public class AppDelegate: ExpoAppDelegate';
12+
contents = contents.replace(replaceText, `${replaceText}, RNAppAuthAuthorizationFlowManager`);
13+
14+
const replaceText2 =
15+
'return super.application(app, open: url, options: options) || RCTLinkingManager.application(app, open: url, options: options)';
16+
contents = contents.replace(
17+
replaceText2,
18+
`if let authorizationFlowManagerDelegate = self.authorizationFlowManagerDelegate {
19+
if authorizationFlowManagerDelegate.resumeExternalUserAgentFlow(with: url) {
20+
return true
21+
}
22+
}
23+
${replaceText2}`
24+
);
25+
26+
const replaceText3 = 'var reactNativeFactory: RCTReactNativeFactory?';
27+
contents = contents.replace(
28+
replaceText3,
29+
`${replaceText3}\n\n public weak var authorizationFlowManagerDelegate: RNAppAuthAuthorizationFlowManagerDelegate?`
30+
);
31+
}
32+
33+
config.modResults.contents = contents;
34+
return config;
35+
});
36+
};
37+
38+
const withAppAuthAppDelegate = rootConfig => {
39+
if (isExpo53OrLater(rootConfig)) {
40+
return withAppDelegateSwift(rootConfig);
41+
}
42+
43+
return withAppDelegate(rootConfig, config => {
44+
let { contents } = config.modResults;
45+
46+
// insert the code that handles the custom scheme redirections
47+
contents = codeModIOs.insertContentsInsideObjcFunctionBlock(
48+
contents,
49+
'application:openURL:options:',
50+
`// react-native-app-auth
51+
if ([self.authorizationFlowManagerDelegate resumeExternalUserAgentFlowWithURL:url]) {
52+
return YES;
53+
}
54+
`,
55+
{ position: 'head' }
56+
);
57+
58+
config.modResults.contents = contents;
59+
return config;
60+
});
61+
};
62+
63+
module.exports = {
64+
withAppAuthAppDelegate,
65+
};
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
const { withDangerousMod, withXcodeProject } = require('@expo/config-plugins');
5+
6+
const { isExpo53OrLater } = require('../expo-version');
7+
8+
const BRIDGING_HEADER_NAME = 'AppDelegate+RNAppAuth.h';
9+
const BRIDGING_HEADER_CONTENT = '#import "RNAppAuthAuthorizationFlowManager.h"\n';
10+
11+
const findBridgingHeader = dir => {
12+
const files = fs.readdirSync(dir);
13+
14+
// First check current directory
15+
const headerInCurrentDir = files.find(f => f.endsWith('-Bridging-Header.h') || f.endsWith('.h'));
16+
if (headerInCurrentDir) {
17+
return path.join(dir, headerInCurrentDir);
18+
}
19+
20+
// Then check subdirectories
21+
for (const file of files) {
22+
const fullPath = path.join(dir, file);
23+
if (fs.statSync(fullPath).isDirectory()) {
24+
const found = findBridgingHeader(fullPath);
25+
if (found) {
26+
return found;
27+
}
28+
}
29+
}
30+
31+
return null;
32+
};
33+
34+
const withBridgingHeader = rootConfig => {
35+
if (!isExpo53OrLater(rootConfig)) {
36+
return rootConfig;
37+
}
38+
39+
return withDangerousMod(rootConfig, [
40+
'ios',
41+
config => {
42+
const iosPath = path.join(config.modRequest.projectRoot, 'ios');
43+
44+
// Search for existing bridging header in the project and subfolders
45+
const existingHeaderPath = findBridgingHeader(iosPath);
46+
const importLine = BRIDGING_HEADER_CONTENT;
47+
let headerPath;
48+
49+
if (existingHeaderPath) {
50+
headerPath = existingHeaderPath;
51+
const content = fs.readFileSync(headerPath, 'utf8');
52+
53+
if (!content.includes(importLine)) {
54+
fs.writeFileSync(headerPath, `${importLine}\n${content}`);
55+
}
56+
} else {
57+
// Default to new file if none found
58+
headerPath = path.join(iosPath, BRIDGING_HEADER_NAME);
59+
fs.writeFileSync(headerPath, `${importLine}\n`);
60+
config._createdBridgingHeader = BRIDGING_HEADER_NAME;
61+
}
62+
63+
return config;
64+
},
65+
]);
66+
};
67+
68+
const withXcodeBuildSettings = rootConfig =>
69+
withXcodeProject(rootConfig, config => {
70+
const project = config.modResults;
71+
const target = project.getFirstTarget().uuid;
72+
73+
const currentSetting = project.getBuildProperty('SWIFT_OBJC_BRIDGING_HEADER', target);
74+
75+
if (!currentSetting && config._createdBridgingHeader) {
76+
project.addBuildProperty(
77+
'SWIFT_OBJC_BRIDGING_HEADER',
78+
`$(SRCROOT)/${config._createdBridgingHeader}`,
79+
target
80+
);
81+
}
82+
83+
return config;
84+
});
85+
86+
module.exports = {
87+
withBridgingHeader,
88+
withXcodeBuildSettings,
89+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const { withAppAuthAppDelegateHeader } = require('./app-delegate-header');
2+
const { withAppAuthAppDelegate } = require('./app-delegate');
3+
const { withUrlSchemes } = require('./info-plist');
4+
const { withBridgingHeader, withXcodeBuildSettings } = require('./bridging-header');
5+
6+
module.exports = {
7+
withAppAuthAppDelegate,
8+
withAppAuthAppDelegateHeader,
9+
withUrlSchemes,
10+
withBridgingHeader,
11+
withXcodeBuildSettings,
12+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const { withInfoPlist } = require('@expo/config-plugins');
2+
3+
const withUrlSchemes = (config, props) => {
4+
return withInfoPlist(config, cfg => {
5+
cfg.ios.infoPlist.CFBundleURLTypes.push({
6+
CFBundleURLName: '$(PRODUCT_BUNDLE_IDENTIFIER)',
7+
CFBundleURLSchemes: [props?.ios?.urlScheme],
8+
});
9+
10+
return cfg;
11+
});
12+
};
13+
14+
module.exports = {
15+
withUrlSchemes,
16+
};

0 commit comments

Comments
 (0)