UNPKG

smartech-base-expo-plugin

Version:

Smartech Base Expo SDK's React Native Plugin For React Native Projects.

316 lines (315 loc) 15.9 kB
"use strict"; 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; }); };