Skip to content

Commit 33eedd5

Browse files
committed
feat: Add a config plugin for Expo (iOS)
This patch adds an Expo config-plugin to easily embed the library into an Expo project. This plugin with insert the code changes described in the README each time the "prebuild" phase in run.
1 parent ccd565f commit 33eedd5

8 files changed

+398
-4
lines changed

Diff for: README.md

+13
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,19 @@ If you want to support universal links, add the following to `AppDelegate.m` und
434434
+ }
435435
```
436436
437+
##### For Expo projects
438+
439+
Add the following line to your [config plugins declaration](https://docs.expo.dev/config-plugins/introduction/) in the
440+
`app.json` file at the root of the Expo project (can be `app.config.js` or `app.config.ts` if you are using dynamic config):
441+
442+
```json
443+
{
444+
"expo": {
445+
"plugins": ["react-native-app-auth/plugin"]
446+
}
447+
}
448+
```
449+
437450
#### Integration of the library with a Swift iOS project
438451
439452
The approach mentioned should work with Swift. In this case one should make `AppDelegate` conform to `RNAppAuthAuthorizationFlowManager`. Note that this is not tested/guaranteed by the maintainers.

Diff for: package.json

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"react-native": ">=0.63.0"
4848
},
4949
"devDependencies": {
50+
"@expo/config-plugins": "^7.2.5",
5051
"@svitejs/changesets-changelog-github-compact": "^0.1.1",
5152
"babel-eslint": "10.0.3",
5253
"eslint": "6.8.0",

Diff for: plugin/index.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const { withPlugins, createRunOncePlugin } = require('@expo/config-plugins');
2+
const { withAppAuthAppDelegate, withAppAuthAppDelegateHeader } = require('./ios');
3+
4+
const withAppAuth = config => {
5+
return withPlugins(config, [
6+
// iOS
7+
withAppAuthAppDelegate,
8+
withAppAuthAppDelegateHeader, // 👈 ️this one uses withDangerousMod !
9+
]);
10+
};
11+
12+
const packageJson = require('../package.json');
13+
module.exports = createRunOncePlugin(withAppAuth, packageJson.name, packageJson.version);

Diff for: plugin/ios/app-delegate-header.js

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
const { IOSConfig, withDangerousMod } = require('@expo/config-plugins');
2+
const codeModIOs = require('@expo/config-plugins/build/ios/codeMod');
3+
const {
4+
createGeneratedHeaderComment,
5+
removeContents,
6+
} = require('@expo/config-plugins/build/utils/generateCode');
7+
const fs = require('fs');
8+
const { insertProtocolDeclaration } = require('./utils/insert-protocol-declaration');
9+
10+
const withAppAuthAppDelegateHeader = rootConfig =>
11+
withDangerousMod(rootConfig, [
12+
'ios',
13+
config => {
14+
// find the AppDelegate.h file in the project
15+
const headerFilePath = IOSConfig.Paths.getAppDelegateObjcHeaderFilePath(
16+
config.modRequest.projectRoot
17+
);
18+
19+
// BEWARE: we update the AppDelegate.h file *outside* of the standard Expo config procedure !
20+
let contents = fs.readFileSync(headerFilePath, 'utf-8');
21+
22+
// add a new import (unless it already exists)
23+
contents = codeModIOs.addObjcImports(contents, ['"RNAppAuthAuthorizationFlowManager.h"']);
24+
25+
// adds a new protocol to the AppDelegate interface (unless it already exists)
26+
contents = insertProtocolDeclaration({
27+
source: contents,
28+
interfaceName: 'AppDelegate',
29+
protocolName: 'RNAppAuthAuthorizationFlowManager',
30+
baseClassName: 'EXAppDelegateWrapper',
31+
});
32+
33+
// add a new property to the AppDelegate interface (unless it already exists)
34+
contents = removeContents({
35+
src: contents,
36+
tag: 'react-native-app-auth',
37+
}).contents;
38+
contents = codeModIOs.insertContentsInsideObjcInterfaceBlock(
39+
contents,
40+
'@interface AppDelegate',
41+
`
42+
${createGeneratedHeaderComment(contents, 'react-native-app-auth', '//')}
43+
@property(nonatomic, weak) id<RNAppAuthAuthorizationFlowManagerDelegate> authorizationFlowManagerDelegate;
44+
// @generated end react-native-app-auth`,
45+
{ position: 'head' }
46+
);
47+
48+
// and finally we write the file back to the disk
49+
fs.writeFileSync(headerFilePath, contents, 'utf-8');
50+
51+
return config;
52+
},
53+
]);
54+
55+
module.exports = {
56+
withAppAuthAppDelegateHeader,
57+
};

Diff for: plugin/ios/app-delegate.js

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
const { withAppDelegate } = require('@expo/config-plugins');
2+
const codeModIOs = require('@expo/config-plugins/build/ios/codeMod');
3+
const {
4+
createGeneratedHeaderComment,
5+
removeContents,
6+
} = require('@expo/config-plugins/build/utils/generateCode');
7+
8+
const withAppAuthAppDelegate = rootConfig =>
9+
withAppDelegate(rootConfig, config => {
10+
let { contents } = config.modResults;
11+
12+
// generation tags & headers
13+
const tag1 = 'react-native-app-auth custom scheme';
14+
const tag2 = 'react-native-app-auth deep linking';
15+
const header1 = createGeneratedHeaderComment(contents, tag1, '//');
16+
const header2 = createGeneratedHeaderComment(contents, tag2, '//');
17+
18+
// insert the code that handles the custom scheme redirections
19+
contents = removeContents({ src: contents, tag: tag1 }).contents;
20+
contents = codeModIOs.insertContentsInsideObjcFunctionBlock(
21+
contents,
22+
'application:openURL:options:',
23+
` ${header1}
24+
if ([self.authorizationFlowManagerDelegate resumeExternalUserAgentFlowWithURL:url]) {
25+
return YES;
26+
}
27+
// @generated end ${tag1}`,
28+
{ position: 'head' }
29+
);
30+
31+
// insert the code that handles the deep linking continuation
32+
contents = removeContents({ src: contents, tag: tag2 }).contents;
33+
contents = codeModIOs.insertContentsInsideObjcFunctionBlock(
34+
contents,
35+
'application:continueUserActivity:restorationHandler:',
36+
` ${header2}
37+
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
38+
if (self.authorizationFlowManagerDelegate) {
39+
BOOL resumableAuth = [self.authorizationFlowManagerDelegate resumeExternalUserAgentFlowWithURL:userActivity.webpageURL];
40+
if (resumableAuth) {
41+
return YES;
42+
}
43+
}
44+
}
45+
// @generated end ${tag2}`,
46+
{ position: 'head' }
47+
);
48+
49+
config.modResults.contents = contents;
50+
return config;
51+
});
52+
53+
module.exports = {
54+
withAppAuthAppDelegate,
55+
};

Diff for: plugin/ios/index.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const { withAppAuthAppDelegateHeader } = require('./app-delegate-header');
2+
const { withAppAuthAppDelegate } = require('./app-delegate');
3+
4+
module.exports = {
5+
withAppAuthAppDelegate,
6+
withAppAuthAppDelegateHeader,
7+
};

Diff for: plugin/ios/utils/insert-protocol-declaration.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Inserts a protocol into an Objective-C class interface declaration.
3+
* @param {string} source source code of the file
4+
* @param {string} interfaceName Name of the interface to insert the protocol into (ex: AppDelegate)
5+
* @param {string} protocolName Name of the protocol to add to the list of protocols (ex: RNAppAuthAuthorizationFlowManagerDelegate)
6+
* @param {string|undefined} baseClassName Base class name of the interface (ex: NSObject)
7+
* @returns {string} the patched source code
8+
*/
9+
const insertProtocolDeclaration = ({
10+
source,
11+
interfaceName,
12+
protocolName,
13+
baseClassName = 'NSObject',
14+
}) => {
15+
const matchInterfaceDeclarationRegexp = new RegExp(
16+
`(@interface\\s+${interfaceName}\\s*:\\s*${baseClassName})(\\s*\\<(.*)\\>)?`
17+
);
18+
const match = source.match(matchInterfaceDeclarationRegexp);
19+
if (match) {
20+
const [line, interfaceDeclaration, , existingProtocols] = match;
21+
if (!existingProtocols || !existingProtocols.includes(protocolName)) {
22+
source = source.replace(
23+
line,
24+
`${interfaceDeclaration} <${
25+
existingProtocols ? `${existingProtocols},` : ''
26+
}${protocolName}>`
27+
);
28+
}
29+
}
30+
return source;
31+
};
32+
33+
module.exports = {
34+
insertProtocolDeclaration,
35+
};

0 commit comments

Comments
 (0)