import { ConfigPlugin, AndroidConfig, BaseMods, Mod, withMod, } from '@expo/config-plugins'; import { ExpoConfig } from '@expo/config-types'; import fs from 'fs'; import { generateNotificationIconImageFiles, notificationIconResource, } from './helpers/notificationIcon'; const generateFilesAndReturnResource = async ( iconPath: string | undefined, projectRoot: string, suffix: string, ) => { if (!iconPath) return undefined; await generateNotificationIconImageFiles(iconPath, projectRoot, suffix); return notificationIconResource(suffix); }; const integerFromHexColor = (hexOrig: string) => { let hex = hexOrig; // Remove leading '#' if (hex.startsWith('#')) { hex = hex.slice(1); } // If shorthand (like 'fff'), expand to 6-digit form if (hex.length === 3) { hex = [hex[0], hex[0], hex[1], hex[1], hex[2], hex[2]].join(''); } // If missing the leading two alpha digits, add 'ff' for full opacity if (hex.length === 6) { hex = `ff${hex}`; } // Guard for invalid input if (hex.length !== 8) { throw new Error( `Invalid format for color. Should be in hex form like #fff, #aabbcc, or #aabbccdd. Instead got ${hexOrig}`, ); } // Add leading hex prefix (Java) hex = `0x${hex}`; return hex; }; const generateBrazeXmlContents = async ( { androidSdkApiKey, androidSdkEndpoint, firebaseCloudMessagingSenderId, smallNotificationIcon, largeNotificationIcon, notificationIconBackgroundColor, }: ConfigProps, projectRoot: string, ) => { const smallIconResource = await generateFilesAndReturnResource( smallNotificationIcon, projectRoot, 'small', ); const largeIconResource = await generateFilesAndReturnResource( largeNotificationIcon, projectRoot, 'large', ); const color = notificationIconBackgroundColor && integerFromHexColor(notificationIconBackgroundColor); return ` ${androidSdkApiKey} ${androidSdkEndpoint} true ${firebaseCloudMessagingSenderId} true ${ smallIconResource ? `${smallIconResource}` : '' } ${ largeIconResource ? `${largeIconResource}` : '' } ${ notificationIconBackgroundColor ? `${color}` : '' } `.trim(); }; /** * A helper which adds the brazeXml mod */ export const withBrazeXmlBaseMod: ConfigPlugin = ( config, props, ) => { return BaseMods.withGeneratedBaseMods(config, { platform: 'android', providers: { // Append a custom rule to supply AppDelegate header data to mods on `mods.android.brazeXml` brazeXml: BaseMods.provider({ // Get the local filepath for res/values/braze.xml async getFilePath({ modRequest: { projectRoot } }) { const resourceFolder = await AndroidConfig.Paths.getResourceFolderAsync(projectRoot); return `${resourceFolder}/values/braze.xml`; }, // This 'read' method is required, but we skip this since we don't expect the file to exist async read() { return ''; }, // Write braze.xml to the filesystem. async write(filePath: string, { modRequest: { projectRoot } }) { const brazeXmlContents = await generateBrazeXmlContents( props, projectRoot, ); await fs.promises.writeFile(filePath, brazeXmlContents); }, }), }, }); }; /** * (Utility) Wraps the brazeXml base mod */ const withBrazeXmlMod = (config: ExpoConfig, action: Mod) => { return withMod(config, { platform: 'android', mod: 'brazeXml', action, }); }; /** * Mod to execute the brazeXml base mod */ export const withBrazeXml: ConfigPlugin = (configOuter) => withBrazeXmlMod(configOuter, (config) => config);