Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions sdk/expo/src/apple/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { withMoEngageInfoPlist } from "./withInfoPlist";
import { withMoEngageEntitlements } from "./withEntitlements";
import { withMoEngageXcodeProject } from "./withXcodeProject";
import { withMoEngageDangerousMod } from "./withDangerousMod";
import * as constants from './constants';
import * as constants from "./constants";
import { withEASManagedProject } from "./withEasManagedProject";

/**
* The main iOS plugin that applies all modifiers to set up MoEngage iOS integration
Expand All @@ -17,22 +18,32 @@ import * as constants from './constants';
* 2. withMoEngageEntitlements - Sets up app groups and other entitlements
* 3. withMoEngageXcodeProject - Configures the Xcode project with extension targets
* 4. withMoEngageDangerousMod - Performs file operations and updates Podfile
* 5. withEASManagedProject - Adds any targets to config.extra.eas.build.experimental.ios.appExtensions, to assist with automatic provisioning profile generation via EAS build.
*
* @param config - The Expo config object
* @param props - The MoEngage plugin properties
* @returns The fully configured Expo config
*/
export const withMoEngageIos: ConfigPlugin<MoEngagePluginProps> = (config, props) => {
export const withMoEngageIos: ConfigPlugin<MoEngagePluginProps> = (
config,
props
) => {
config = withMoEngageInfoPlist(config, props);
config = withMoEngageEntitlements(config, props);
config = withMoEngageXcodeProject(config, props);
config = withMoEngageDangerousMod(config, props);
config = withEASManagedProject(config, props);
return config;
};

// Export all constants and individual modifiers for advanced usage
export { constants };
export { withMoEngageInfoPlist, withMoEngageEntitlements, withMoEngageXcodeProject, withMoEngageDangerousMod };
export {
withMoEngageInfoPlist,
withMoEngageEntitlements,
withMoEngageXcodeProject,
withMoEngageDangerousMod,
};

// Default export
export default withMoEngageIos;
45 changes: 45 additions & 0 deletions sdk/expo/src/apple/injectAppExtensionIntoEasManagedExtra.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ExpoConfig } from "@expo/config-types";
import { EasManagedExtra } from "../types";

/**
* Taps into the EAS build process, exposing what targets are being built
* so EAS build an automatically generate/maintain the appropriate provisioning profiles for the app
* More info here: https://docs.expo.dev/build-reference/app-extensions/
*/
export default function injectAppExtensionIntoEasManagedExtra(
config: ExpoConfig,
injectedValue: {
targetName: string;
bundleIdentifier: string;
entitlements: Record<string, string[]>;
}
): ExpoConfig["extra"] {
const easExtra = (config.extra ?? {}) as EasManagedExtra;
easExtra.eas ??= {};
easExtra.eas.build ??= {};
easExtra.eas.build.experimental ??= {};
easExtra.eas.build.experimental.ios ??= {};
easExtra.eas.build.experimental.ios.appExtensions = [];

config.extra = easExtra;

return {
...easExtra,
eas: {
...easExtra?.eas,
build: {
...easExtra?.eas?.build,
experimental: {
...easExtra.eas?.build?.experimental,
ios: {
...easExtra.eas?.build?.experimental?.ios,
appExtensions: [
...(easExtra.eas?.build?.experimental?.ios?.appExtensions ?? []),
injectedValue,
],
},
},
},
},
};
}
170 changes: 170 additions & 0 deletions sdk/expo/src/apple/withEASManagedProject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import {
ConfigPlugin,
ExportedConfigWithProps,
withDangerousMod,
} from "@expo/config-plugins";
import { AppExtension, MoEngagePluginProps } from "../types";
import * as fs from "fs";
import * as path from "path";
import { ExpoConfig } from "@expo/config-types";
import { EasManagedExtra } from "../types";
import {
MOENGAGE_IOS_LIVE_ACTIVITY_TARGET,
MOENGAGE_IOS_PUSH_TEMPLATE_TARGET,
MOENGAGE_IOS_RICH_PUSH_TARGET,
} from "./constants";
const plist = require("plist");

const isTargetAlreadyPresent = (extra: EasManagedExtra, target: string) => {
const extension = extra.eas?.build?.experimental?.ios?.appExtensions?.find(
(extension: { targetName?: string }) => extension.targetName === target
);
if (extension) {
console.log(
`Target '${target}' already present in eas appExtensions. Skipping appExtension autogeneration....`
);
}
return Boolean(extension);
};

/**
* Taps into the EAS build process, exposing what targets are being built
* so EAS build an automatically generate/maintain the appropriate provisioning profiles for the app
* More info here: https://docs.expo.dev/build-reference/app-extensions/
*/
export default function injectAppExtensionIntoEasManagedExtra(
config: ExpoConfig,
injectedValue: AppExtension
): EasManagedExtra {
assertTargetNotPresent(
config.extra as EasManagedExtra,
injectedValue.bundleIdentifier
);

const easExtra = (config.extra ?? {}) as EasManagedExtra;
easExtra.eas ??= {};
easExtra.eas.build ??= {};
easExtra.eas.build.experimental ??= {};
easExtra.eas.build.experimental.ios ??= {};
easExtra.eas.build.experimental.ios.appExtensions = [];

config.extra = easExtra;

return {
...easExtra,
eas: {
...easExtra?.eas,
build: {
...easExtra?.eas?.build,
experimental: {
...easExtra.eas?.build?.experimental,
ios: {
...easExtra.eas?.build?.experimental?.ios,
appExtensions: [
...(easExtra.eas?.build?.experimental?.ios?.appExtensions ?? []),
injectedValue,
],
},
},
},
},
};
}

const getAppGroupName = (
exportedConfig: ExportedConfigWithProps<unknown>,
filePath: string
) => {
try {
const configFilePath = path.join(
exportedConfig.modRequest.projectRoot,
filePath
);
if (fs.existsSync(configFilePath)) {
const configPlist = plist.parse(
fs.readFileSync(configFilePath, "utf8")
) as { [key: string]: any };
return configPlist["AppGroupName"] as string;
} else {
const message = `MoEngage configuration does not exist`;
console.error(message);
throw new Error(message);
}
} catch (e) {
const message = `Could not import MoEngage configuration: ${e}`;
console.error(message);
throw new Error(message);
}
};

export const withEASManagedProject: ConfigPlugin<MoEngagePluginProps> = (
config,
props
) => {
return withDangerousMod(config, [
"ios",
(exportedConfig) => {
const { apple } = props;
let easExtra = (exportedConfig.extra ?? {}) as EasManagedExtra;
easExtra.eas ??= {};
easExtra.eas.build ??= {};
easExtra.eas.build.experimental ??= {};
easExtra.eas.build.experimental.ios ??= {};
easExtra.eas.build.experimental.ios.appExtensions ??= [];

const shouldAddRichPushExtension =
apple.richPushNotificationEnabled ||
apple.pushNotificationImpressionTrackingEnabled ||
apple.pushTemplatesEnabled;

const appGroupName = getAppGroupName(
exportedConfig,
apple.configFilePath
);
if (!exportedConfig.ios?.bundleIdentifier) {
const message = `Could not find bundle identifier under 'ios' key`;
console.error(message);
throw new Error(message);
}
if (
shouldAddRichPushExtension &&
!isTargetAlreadyPresent(easExtra, MOENGAGE_IOS_RICH_PUSH_TARGET)
) {
const richPushExtensionBundleID = `${exportedConfig.ios.bundleIdentifier}.${MOENGAGE_IOS_RICH_PUSH_TARGET}`;
easExtra = injectAppExtensionIntoEasManagedExtra(config, {
targetName: MOENGAGE_IOS_RICH_PUSH_TARGET,
bundleIdentifier: richPushExtensionBundleID,
entitlements: {
"com.apple.security.application-groups": [appGroupName],
},
});
}
if (
apple.pushTemplatesEnabled &&
!isTargetAlreadyPresent(easExtra, MOENGAGE_IOS_PUSH_TEMPLATE_TARGET)
) {
const templatePushBundleId = `${exportedConfig.ios.bundleIdentifier}.${MOENGAGE_IOS_PUSH_TEMPLATE_TARGET}`;
easExtra = injectAppExtensionIntoEasManagedExtra(config, {
targetName: MOENGAGE_IOS_PUSH_TEMPLATE_TARGET,
bundleIdentifier: templatePushBundleId,
entitlements: {
"com.apple.security.application-groups": [appGroupName],
},
});
}
if (
apple.liveActivityTargetPath &&
apple.liveActivityTargetPath.length &&
!isTargetAlreadyPresent(easExtra, MOENGAGE_IOS_LIVE_ACTIVITY_TARGET)
) {
easExtra = injectAppExtensionIntoEasManagedExtra(config, {
targetName: MOENGAGE_IOS_LIVE_ACTIVITY_TARGET,
bundleIdentifier: exportedConfig.ios.bundleIdentifier,
entitlements: {},
});
}
exportedConfig.extra = easExtra;
return exportedConfig;
},
]);
};
20 changes: 19 additions & 1 deletion sdk/expo/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,22 @@ export interface MoEngagePluginProps {
* iOS-specific configuration options
*/
apple: MoEngageIosConfig;
}
}

export type AppExtension = {
targetName: string;
bundleIdentifier: string;
entitlements: Record<string, string[]>;
};

export type EasManagedExtra = {
eas?: {
build?: {
experimental?: {
ios?: {
appExtensions?: AppExtension[];
};
};
};
};
};