smartech-base-expo-plugin
Version:
Smartech Base Expo SDK's React Native Plugin For React Native Projects.
316 lines (315 loc) • 15.9 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.withNetcoreAndroid = void 0;
const config_plugins_1 = require("@expo/config-plugins");
const fs_1 = require("fs");
const helper_1 = require("./helper");
const androidConstants_1 = require("./androidConstants");
const path_1 = __importDefault(require("path"));
const { addMetaDataItemToMainApplication, getMainApplicationOrThrow } = config_plugins_1.AndroidConfig.Manifest;
const modifyMainApplication = async (filePath, props) => {
const fileExtension = path_1.default.extname(filePath);
let fileContent = await fs_1.promises.readFile(filePath, 'utf-8');
let isKotlinFile = props.android.isKotlinProject;
// adding the import statement
const importStatement = isKotlinFile ? androidConstants_1.smartechInitSnippet.createImportStatementKotlin(props) : androidConstants_1.smartechInitSnippet.createImportStatementJava(props);
if (!fileContent.includes(importStatement)) {
const packageIndex = fileContent.indexOf('package');
const insertIndex = fileContent.indexOf('\n', packageIndex) + 1;
fileContent = `${fileContent.slice(0, insertIndex)}\n${importStatement}\n${fileContent.slice(insertIndex)}`;
}
let onCreateMethodRegex = isKotlinFile ? androidConstants_1.smartechInitSnippet.onCreateMethodRegexKotlin : androidConstants_1.smartechInitSnippet.onCreateMethodRegexJava;
let newArchConditionRegex = isKotlinFile ? androidConstants_1.smartechInitSnippet.newArchConditionRegexKotlin : androidConstants_1.smartechInitSnippet.newArchConditionRegexJava;
const onCreateMatch = fileContent.match(onCreateMethodRegex);
if (!onCreateMatch) {
throw new Error('Could not find the onCreate method in MainApplication.');
}
let onCreateBody = onCreateMatch[0].trim();
const areLogsEnabled = props.android.isLogEnabled;
const smartechInitCodeBlock = props.android.isKotlinProject ? (0, androidConstants_1.smartechInitSnippetKotlin)(props).trim() : (0, androidConstants_1.smartechInitSnippetJava)(props).trim();
// If the flag is true, insert the custom code inside the New Architecture check.
if (props.isNewArchEnabled) {
const newArchCheckRegex = androidConstants_1.smartechInitSnippet.newArchCheckRegex;
const newArchCheckMatch = onCreateBody.match(newArchCheckRegex);
if (newArchCheckMatch) {
const newArchCheckBody = newArchCheckMatch[1].trim();
const modifiedNewArchCheckBody = `${newArchCheckBody}\n ${smartechInitCodeBlock} \n`;
onCreateBody = onCreateBody.replace(newArchCheckBody, modifiedNewArchCheckBody);
}
else {
onCreateBody += `\n ${smartechInitCodeBlock} \n`;
}
}
else {
onCreateBody += `\n ${smartechInitCodeBlock}\n`;
}
// Replace the original onCreate method with the modified one
fileContent = fileContent.replace(onCreateMethodRegex, onCreateBody);
// Save the modified content back to the file
await fs_1.promises.writeFile(filePath, fileContent, 'utf-8');
};
// Expo Config Plugin to modify MainApplication.kt or MainApplication.java
const addSmartechInitCode = (config, props) => {
return (0, config_plugins_1.withDangerousMod)(config, [
'android',
async (config) => {
const isKotlinFile = props.android.isKotlinProject;
const packageName = config.android?.package ?? '';
const mainApplicationJavaPath = `./android/app/src/main/java/${packageName.replace(/\./g, '/')}/MainApplication.java`;
const mainApplicationKotlinPath = `./android/app/src/main/java/${packageName.replace(/\./g, '/')}/MainApplication.kt`;
const mainApplicationPath = isKotlinFile ? mainApplicationKotlinPath : mainApplicationJavaPath;
await modifyMainApplication(mainApplicationPath, props);
return config;
},
]);
};
const addMetaDataToManifest = (config, props) => {
return (0, config_plugins_1.withAndroidManifest)(config, async (config) => {
const androidManifest = config.modResults;
const mainApplication = getMainApplicationOrThrow(androidManifest);
// Adding Smartech MetaData to Manifest
const smartechMetaData = props.android.smartechMetaData;
if (smartechMetaData) {
smartechMetaData.forEach(item => {
addMetaDataItemToMainApplication(mainApplication, item.name, item.value);
});
}
// If autoFetchLocation is enabled then add it inside the manifest file,
if (props.android.autoFetchLocation != undefined) {
addMetaDataItemToMainApplication(mainApplication, androidConstants_1.AutoFetchLocationKey, props.android.autoFetchLocation ? "1" : "0");
}
/**
* Check Hansel is enabled or not, If not then we dont
* add the meta data to manifest.
*/
const isHanselEnabled = props.android.smartechNudges.smartechNudgesEnabled;
if (isHanselEnabled) {
const hanselMetaData = props.android.smartechNudges.smartechNudgesMetaData;
hanselMetaData.forEach(item => {
addMetaDataItemToMainApplication(mainApplication, item.name, item.value);
});
// check for the Encryption value from
addMetaDataItemToMainApplication(mainApplication, androidConstants_1.smartechHanselEncryptionKey, `${props.android.smartechNudges.useEncryption}`);
}
return config;
});
};
// Helper function to check if the dependency exists in the contents
function containsDependency(contents, dependency) {
const regex = new RegExp(`api\\s+"${dependency}[^"]*"`);
return regex.test(contents);
}
// Set custom Android build.gradle modifications
const addSmartechDependency = (config, props) => {
return (0, config_plugins_1.withDangerousMod)(config, [
'android',
async (config) => {
const appBuildGradleContents = await fs_1.promises.readFile(androidConstants_1.appBuildGradlePath, 'utf-8');
let modifiedAppContents = appBuildGradleContents.replace('dependencies {', `${androidConstants_1.repositorySnippet}\n\ndependencies {`);
// Adding Smartech Base dependency in build.gradle
modifiedAppContents = helper_1.Helper.Android.addDependency(modifiedAppContents, androidConstants_1.dependency.smartechBase, props.android.SMARTECH_BASE_SDK_VERSION);
await fs_1.promises.writeFile(androidConstants_1.appBuildGradlePath, modifiedAppContents, 'utf-8');
return config;
}
]);
};
const modifyMainActivityForTestDevice = (content, addTestDeviceHansel, addTestDeviceSmartech, isKotlin) => {
const superOnCreate = 'super.onCreate';
const superIndex = content.indexOf(superOnCreate);
const insertIndex = content.indexOf(')', superIndex) + 1;
if (superIndex === -1) {
console.warn('super.onCreate not found in MainActivity');
return { modifiedContent: content, importsAdded: [] };
}
let modified = content;
// Implementing the AddTestDevice code
const linesToInsert = [];
const importsAdded = [];
const hanselSnippetUpdated = (0, androidConstants_1.hanselTestDeviceUpdatedSnippet)(isKotlin);
const smartechSnippet = (0, androidConstants_1.smartechTestDeviceSnippet)(isKotlin);
if (addTestDeviceHansel) {
linesToInsert.push(hanselSnippetUpdated.methodStatement);
importsAdded.push(hanselSnippetUpdated.importStatement);
}
if (addTestDeviceSmartech) {
linesToInsert.push(smartechSnippet.methodStatement);
importsAdded.push(smartechSnippet.importStatement);
}
// Add lines to onCreate if any
if (linesToInsert.length > 0) {
const indent = content.substring(0, superIndex).match(/\s*$/)?.[0] || ' ';
const formattedLines = linesToInsert
.map(line => `${indent}${line}`)
.join('\n');
modified = [
modified.slice(0, insertIndex),
`\n${formattedLines}`,
modified.slice(insertIndex),
].join('');
}
return { modifiedContent: modified, importsAdded };
};
const addTestDevice = (config, props) => {
return (0, config_plugins_1.withDangerousMod)(config, [
'android',
async (config) => {
const isKotlin = props.android.isKotlinProject;
const isHanselEnabled = props.android.smartechNudges.smartechNudgesEnabled;
const addTestDeviceForHansel = props.android.smartechNudges.addTestDevice ?? false;
const addTestDeviceForSmartech = props.android.addTestDevice ?? false;
const packageName = config.android?.package ?? '';
const mainActivityDir = path_1.default.resolve(config.modRequest.projectRoot, `android/app/src/main/java/${packageName.replace(/\./g, '/')}`);
const mainActivityPath = path_1.default.join(mainActivityDir, isKotlin ? 'MainActivity.kt' : 'MainActivity.java');
// Check if the file exists
try {
let content = await fs_1.promises.readFile(mainActivityPath, 'utf-8');
const { modifiedContent, importsAdded } = modifyMainActivityForTestDevice(content, (isHanselEnabled && addTestDeviceForHansel), addTestDeviceForSmartech, isKotlin);
// Add imports at the top if any
if (importsAdded.length > 0) {
const packageIndex = modifiedContent.indexOf('package');
if (packageIndex === -1)
throw new Error('Package statement not found');
const insertIndex = modifiedContent.indexOf('\n', packageIndex) + 1;
content = [
modifiedContent.slice(0, insertIndex),
importsAdded.join('\n') + '\n',
modifiedContent.slice(insertIndex),
].join('');
}
else {
content = modifiedContent;
}
await fs_1.promises.writeFile(mainActivityPath, content, 'utf-8');
}
catch (err) {
console.error(`MainActivity file does not exist at path: ${mainActivityPath}`);
}
return config;
},
]);
};
// Main plugin function to create xml directory and copy the native.xml file
const withAndroidXMLFilesBackup = (config, props) => {
try {
const allowBackup = config.android?.allowBackup ?? false;
// If allowBackup is not true, skip the rest of the operations
if (!allowBackup) {
return config;
}
const targetSdkVersion = config.plugins?.find(([pluginName]) => pluginName === 'expo-build-properties')?.[1]?.android?.targetSdkVersion || 31; // Fallback to 31 if not defined
config = (0, config_plugins_1.withDangerousMod)(config, ['android', async (config) => {
// Path to the React Native app's assets/native.xml file
const projectRoot = config.modRequest.projectRoot;
// Read the backup folder path from the config file
const backupAssetFolder = props.android.backupXMLFiles;
if (!backupAssetFolder || backupAssetFolder.length === 0) {
console.log('backupXMLFiles path is either undefined or empty');
return config;
}
// Construct the full path to the backup folder
const backupFolder = path_1.default.join(projectRoot, backupAssetFolder);
// Ensure the folder exists
await ensureDirectoryExists(backupFolder);
const sourceFile = path_1.default.join(backupAssetFolder, 'backup.xml');
const sourceFile31 = path_1.default.join(backupAssetFolder, 'backup_31.xml');
// Path to the Android res/xml directory
const xmlDir = path_1.default.join(projectRoot, 'android', 'app', 'src', 'main', 'res', 'xml');
// Ensure the xml directory exists
ensureDirectoryExists(xmlDir);
// Destination path for the native.xml file inside res/xml
const destinationFile = path_1.default.join(xmlDir, 'backup.xml');
copyFile(sourceFile, destinationFile);
if (targetSdkVersion >= 31) {
// Destination path for the native.xml file inside res/xml
const destinationFile31 = path_1.default.join(xmlDir, 'backup_31.xml');
copyFile(sourceFile31, destinationFile31);
}
return config;
}]);
// Modify the AndroidManifest.xml
config = (0, config_plugins_1.withAndroidManifest)(config, (config) => {
const application = config.modResults.manifest.application?.[0];
if (application) {
application['$']['android:fullBackupContent'] = '@xml/backup';
if (targetSdkVersion >= 31) {
application['$']['android:dataExtractionRules'] = '@xml/backup_31';
}
}
return config;
});
}
catch (error) {
console.error("Error in withAndroidXMLFilesBackup function");
}
return config;
};
// Utility to ensure directory exists asynchronously
const ensureDirectoryExists = async (directory) => {
try {
await fs_1.promises.access(directory);
}
catch (error) {
await fs_1.promises.mkdir(directory, { recursive: true });
}
};
// Utility to copy a file asynchronously if it doesn't already exist
const copyFile = async (source, destination) => {
try {
await fs_1.promises.access(source);
try {
await fs_1.promises.access(destination);
console.log(`${destination} already exists. Skipping file copy.`);
}
catch (error) {
// Destination does not exist, proceed to copy
await fs_1.promises.copyFile(source, destination);
}
}
catch (error) {
console.warn(`Source file ${source} does not exist`);
}
};
const withNetcoreAndroid = (config, props) => {
try {
config = withAndroidXMLFilesBackup(config, props);
config = addMetaDataToManifest(config, props);
config = updateGradlePropertise(config, props);
config = addSmartechDependency(config, props);
config = addSmartechInitCode(config, props);
config = addTestDevice(config, props);
}
catch (error) {
console.error('Failed to add smartech base expo plugin custom code with error:', error);
}
return config;
};
exports.withNetcoreAndroid = withNetcoreAndroid;
/**
*
* This method will add the Smartech gradle dependencies to the build.gradle.
*
* @param config Expo Config
* @param props SmartechBaseProps
* @returns modified config
*/
const updateGradlePropertise = (config, props) => {
return (0, config_plugins_1.withGradleProperties)(config, async (config) => {
const customProperties = [
{ key: 'SMARTECH_BASE_SDK_VERSION', value: props.android.SMARTECH_BASE_SDK_VERSION }
];
// Update the modResults with custom properties
for (const property of customProperties) {
if (!config.modResults.some((item) => item.type === 'property' && item.key === property.key)) {
config.modResults.push({
type: 'property',
key: property.key,
value: property.value,
});
}
}
return config;
});
};