Skip to content

Commit

Permalink
feat: Add a config plugin for Expo (iOS)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
aurelien-iapp committed Sep 6, 2024
1 parent 9f586bd commit 673eb65
Show file tree
Hide file tree
Showing 7 changed files with 1,990 additions and 1,628 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
},
"devDependencies": {
"@changesets/cli": "^2.26.1",
"@expo/config-plugins": "^7.2.5",
"@svitejs/changesets-changelog-github-compact": "^0.1.1",
"babel-eslint": "10.0.3",
"eslint": "6.8.0",
Expand Down
13 changes: 13 additions & 0 deletions plugin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const { withPlugins, createRunOncePlugin } = require('@expo/config-plugins');
const { withAppAuthAppDelegate, withAppAuthAppDelegateHeader } = require('./ios');

const withAppAuth = config => {
return withPlugins(config, [
// iOS
withAppAuthAppDelegate,
withAppAuthAppDelegateHeader, // 👈 ️this one uses withDangerousMod !
]);
};

const packageJson = require('../package.json');
module.exports = createRunOncePlugin(withAppAuth, packageJson.name, packageJson.version);
57 changes: 57 additions & 0 deletions plugin/ios/app-delegate-header.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const { IOSConfig, withDangerousMod } = require('@expo/config-plugins');
const codeModIOs = require('@expo/config-plugins/build/ios/codeMod');
const {
createGeneratedHeaderComment,
removeContents,
} = require('@expo/config-plugins/build/utils/generateCode');
const fs = require('fs');
const { insertProtocolDeclaration } = require('./utils/insert-protocol-declaration');

const withAppAuthAppDelegateHeader = rootConfig =>
withDangerousMod(rootConfig, [
'ios',
config => {
// find the AppDelegate.h file in the project
const headerFilePath = IOSConfig.Paths.getAppDelegateObjcHeaderFilePath(
config.modRequest.projectRoot
);

// BEWARE: we update the AppDelegate.h file *outside* of the standard Expo config procedure !
let contents = fs.readFileSync(headerFilePath, 'utf-8');

// add a new import (unless it already exists)
contents = codeModIOs.addObjcImports(contents, ['"RNAppAuthAuthorizationFlowManager.h"']);

// adds a new protocol to the AppDelegate interface (unless it already exists)
contents = insertProtocolDeclaration({
source: contents,
interfaceName: 'AppDelegate',
protocolName: 'RNAppAuthAuthorizationFlowManager',
baseClassName: 'EXAppDelegateWrapper',
});

// add a new property to the AppDelegate interface (unless it already exists)
contents = removeContents({
src: contents,
tag: 'react-native-app-auth',
}).contents;
contents = codeModIOs.insertContentsInsideObjcInterfaceBlock(
contents,
'@interface AppDelegate',
`
${createGeneratedHeaderComment(contents, 'react-native-app-auth', '//')}
@property(nonatomic, weak) id<RNAppAuthAuthorizationFlowManagerDelegate> authorizationFlowManagerDelegate;
// @generated end react-native-app-auth`,
{ position: 'head' }
);

// and finally we write the file back to the disk
fs.writeFileSync(headerFilePath, contents, 'utf-8');

return config;
},
]);

module.exports = {
withAppAuthAppDelegateHeader,
};
55 changes: 55 additions & 0 deletions plugin/ios/app-delegate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
const { withAppDelegate } = require('@expo/config-plugins');
const codeModIOs = require('@expo/config-plugins/build/ios/codeMod');
const {
createGeneratedHeaderComment,
removeContents,
} = require('@expo/config-plugins/build/utils/generateCode');

const withAppAuthAppDelegate = rootConfig =>
withAppDelegate(rootConfig, config => {
let { contents } = config.modResults;

// generation tags & headers
const tag1 = 'react-native-app-auth custom scheme';
const tag2 = 'react-native-app-auth deep linking';
const header1 = createGeneratedHeaderComment(contents, tag1, '//');
const header2 = createGeneratedHeaderComment(contents, tag2, '//');

// insert the code that handles the custom scheme redirections
contents = removeContents({ src: contents, tag: tag1 }).contents;
contents = codeModIOs.insertContentsInsideObjcFunctionBlock(
contents,
'application:openURL:options:',
` ${header1}
if ([self.authorizationFlowManagerDelegate resumeExternalUserAgentFlowWithURL:url]) {
return YES;
}
// @generated end ${tag1}`,
{ position: 'head' }
);

// insert the code that handles the deep linking continuation
contents = removeContents({ src: contents, tag: tag2 }).contents;
contents = codeModIOs.insertContentsInsideObjcFunctionBlock(
contents,
'application:continueUserActivity:restorationHandler:',
` ${header2}
if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
if (self.authorizationFlowManagerDelegate) {
BOOL resumableAuth = [self.authorizationFlowManagerDelegate resumeExternalUserAgentFlowWithURL:userActivity.webpageURL];
if (resumableAuth) {
return YES;
}
}
}
// @generated end ${tag2}`,
{ position: 'head' }
);

config.modResults.contents = contents;
return config;
});

module.exports = {
withAppAuthAppDelegate,
};
7 changes: 7 additions & 0 deletions plugin/ios/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const { withAppAuthAppDelegateHeader } = require('./app-delegate-header');
const { withAppAuthAppDelegate } = require('./app-delegate');

module.exports = {
withAppAuthAppDelegate,
withAppAuthAppDelegateHeader,
};
35 changes: 35 additions & 0 deletions plugin/ios/utils/insert-protocol-declaration.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Inserts a protocol into an Objective-C class interface declaration.
* @param {string} source source code of the file
* @param {string} interfaceName Name of the interface to insert the protocol into (ex: AppDelegate)
* @param {string} protocolName Name of the protocol to add to the list of protocols (ex: RNAppAuthAuthorizationFlowManagerDelegate)
* @param {string|undefined} baseClassName Base class name of the interface (ex: NSObject)
* @returns {string} the patched source code
*/
const insertProtocolDeclaration = ({
source,
interfaceName,
protocolName,
baseClassName = 'NSObject',
}) => {
const matchInterfaceDeclarationRegexp = new RegExp(
`(@interface\\s+${interfaceName}\\s*:\\s*${baseClassName})(\\s*\\<(.*)\\>)?`
);
const match = source.match(matchInterfaceDeclarationRegexp);
if (match) {
const [line, interfaceDeclaration, , existingProtocols] = match;
if (!existingProtocols || !existingProtocols.includes(protocolName)) {
source = source.replace(
line,
`${interfaceDeclaration} <${
existingProtocols ? `${existingProtocols},` : ''
}${protocolName}>`
);
}
}
return source;
};

module.exports = {
insertProtocolDeclaration,
};
Loading

0 comments on commit 673eb65

Please sign in to comment.