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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-native-app-clip",
"version": "0.9.2",
"version": "0.9.3",
"description": "Config plugin to add an App Clip to a React Native iOS app",
"main": "build/index.js",
"types": "build/index.d.ts",
Expand Down
3 changes: 3 additions & 0 deletions plugin/build/cliPlugin.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export declare const run: (config: {
dependencies: Record<string, object>;
}, exclude: string[]) => Promise<void>;
26 changes: 26 additions & 0 deletions plugin/build/cliPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.run = void 0;
const run = async (config, exclude) => {
// Validation
if (typeof config !== "object") {
throw new Error("config must be an object");
}
if (!config.dependencies) {
throw new Error("config must have a dependencies key");
}
if (!Array.isArray(exclude)) {
throw new Error("exclude must be an array");
}
for (const packageName of exclude) {
if (typeof packageName !== "string") {
throw new Error("exclude must be an array of strings");
}
}
// Removing excluded packages
for (const packageName of exclude) {
delete config.dependencies[packageName];
}
console.log(JSON.stringify(config));
};
exports.run = run;
17 changes: 17 additions & 0 deletions plugin/build/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { type ConfigPlugin } from "expo/config-plugins";
declare const withAppClip: ConfigPlugin<{
name?: string;
enabled?: boolean;
bundleIdSuffix?: string;
targetSuffix?: string;
groupIdentifier?: string;
deploymentTarget?: string;
requestEphemeralUserNotification?: boolean;
requestLocationConfirmation?: boolean;
appleSignin?: boolean;
applePayMerchantIds?: string[];
excludedPackages?: string[];
pushNotifications?: boolean;
enableCompression?: boolean;
}>;
export default withAppClip;
61 changes: 61 additions & 0 deletions plugin/build/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const config_plugins_1 = require("expo/config-plugins");
const withConfig_1 = require("./withConfig");
const withEntitlements_1 = require("./withEntitlements");
const withPlist_1 = require("./withPlist");
const withPodfile_1 = require("./withPodfile");
const withXcode_1 = require("./withXcode");
const DeviceFamily_1 = require("@expo/config-plugins/build/ios/DeviceFamily");
const withAppClip = (config, { name, enabled, bundleIdSuffix, targetSuffix, groupIdentifier, deploymentTarget, requestEphemeralUserNotification, requestLocationConfirmation, appleSignin, applePayMerchantIds, excludedPackages, pushNotifications, enableCompression, } = {}) => {
name ??= "Clip";
bundleIdSuffix ??= "Clip";
targetSuffix ??= "Clip";
deploymentTarget ??= "15.1";
requestEphemeralUserNotification ??= false;
requestLocationConfirmation ??= false;
appleSignin ??= false;
enabled ??= true;
pushNotifications ??= false;
if (!enabled) {
return config;
}
if (!config.ios?.bundleIdentifier) {
throw new Error("No bundle identifier specified in app config");
}
const bundleIdentifier = `${config.ios.bundleIdentifier}.${bundleIdSuffix}`;
const targetName = `${config_plugins_1.IOSConfig.XcodeUtils.sanitizedName(config.name)}${targetSuffix}`;
const modifiedConfig = (0, config_plugins_1.withPlugins)(config, [
DeviceFamily_1.withDeviceFamily,
[
withConfig_1.withConfig,
{ targetName, bundleIdentifier, appleSignin, applePayMerchantIds, pushNotifications },
],
[
withEntitlements_1.withEntitlements,
{ targetName, groupIdentifier, appleSignin, applePayMerchantIds, pushNotifications },
],
[withPodfile_1.withPodfile, { targetName, excludedPackages }],
[
withPlist_1.withPlist,
{
targetName,
deploymentTarget,
requestEphemeralUserNotification,
requestLocationConfirmation,
},
],
[
withXcode_1.withXcode,
{
name,
targetName,
bundleIdentifier,
deploymentTarget,
enableCompression,
},
],
]);
return modifiedConfig;
};
exports.default = withAppClip;
8 changes: 8 additions & 0 deletions plugin/build/lib/getAppClipEntitlements.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ExportedConfigWithProps, InfoPlist } from "expo/config-plugins";
export declare function getAppClipEntitlements(iosConfig: ExportedConfigWithProps["ios"], { groupIdentifier, appleSignin, applePayMerchantIds, pushNotifications, }: {
groupIdentifier?: string;
appleSignin: boolean;
applePayMerchantIds?: string[];
pushNotifications: boolean;
}): InfoPlist;
export declare function addApplicationGroupsEntitlement(entitlements: InfoPlist, groupIdentifier?: string): InfoPlist;
39 changes: 39 additions & 0 deletions plugin/build/lib/getAppClipEntitlements.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAppClipEntitlements = getAppClipEntitlements;
exports.addApplicationGroupsEntitlement = addApplicationGroupsEntitlement;
function getAppClipEntitlements(iosConfig, { groupIdentifier, appleSignin, applePayMerchantIds, pushNotifications, }) {
const appBundleIdentifier = iosConfig?.bundleIdentifier;
const entitlements = {
"com.apple.developer.parent-application-identifiers": [
`$(AppIdentifierPrefix)${appBundleIdentifier}`,
],
"com.apple.developer.on-demand-install-capable": true,
};
addApplicationGroupsEntitlement(entitlements, groupIdentifier);
if (appleSignin) {
entitlements["com.apple.developer.applesignin"] = ["Default"];
}
if (applePayMerchantIds) {
entitlements["com.apple.developer.in-app-payments"] = applePayMerchantIds;
}
if (pushNotifications) {
// XCode automatically changes this to "production" when archiving https://stackoverflow.com/a/42293632/4047926
entitlements["aps-environment"] = "development";
}
if (iosConfig?.associatedDomains) {
entitlements["com.apple.developer.associated-domains"] =
iosConfig.associatedDomains;
}
return entitlements;
}
function addApplicationGroupsEntitlement(entitlements, groupIdentifier) {
if (groupIdentifier) {
const existingApplicationGroups = entitlements["com.apple.security.application-groups"] ?? [];
entitlements["com.apple.security.application-groups"] = [
groupIdentifier,
...existingApplicationGroups,
];
}
return entitlements;
}
5 changes: 5 additions & 0 deletions plugin/build/lib/getAppClipIcon.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { ConfigPlugin } from "expo/config-plugins";
export declare function getAppClipIcon(config: Pick<Parameters<ConfigPlugin>[0], "icon" | "ios">): {
name: string;
fileName: string;
} | undefined;
20 changes: 20 additions & 0 deletions plugin/build/lib/getAppClipIcon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getAppClipIcon = getAppClipIcon;
const node_path_1 = __importDefault(require("node:path"));
function getAppClipIcon(config) {
let icon = config.ios?.icon || config.icon;
if (icon && typeof icon === "object" && !icon.light && !icon.dark && !icon.tinted) {
icon = config.icon;
}
if (typeof icon !== "string" || node_path_1.default.extname(icon) !== ".icon") {
return undefined;
}
return {
name: node_path_1.default.basename(icon, ".icon"),
fileName: node_path_1.default.basename(icon),
};
}
8 changes: 8 additions & 0 deletions plugin/build/withConfig.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ConfigPlugin } from "expo/config-plugins";
export declare const withConfig: ConfigPlugin<{
targetName: string;
bundleIdentifier: string;
appleSignin: boolean;
applePayMerchantIds: string[];
pushNotifications: boolean;
}>;
66 changes: 66 additions & 0 deletions plugin/build/withConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.withConfig = void 0;
const getAppClipEntitlements_1 = require("./lib/getAppClipEntitlements");
const withConfig = (config, { targetName, bundleIdentifier, appleSignin, applePayMerchantIds, pushNotifications }) => {
let configIndex = null;
config.extra?.eas?.build?.experimental?.ios?.appExtensions?.forEach((ext, index) => {
if (ext.targetName === targetName) {
configIndex = index;
}
});
if (!configIndex) {
config.extra = {
...config.extra,
eas: {
...config.extra?.eas,
build: {
...config.extra?.eas?.build,
experimental: {
...config.extra?.eas?.build?.experimental,
ios: {
...config.extra?.eas?.build?.experimental?.ios,
appExtensions: [
...(config.extra?.eas?.build?.experimental?.ios
?.appExtensions ?? []),
{
targetName,
bundleIdentifier,
},
],
},
},
},
},
};
configIndex = 0;
}
if (configIndex !== null && config.extra) {
const appClipConfig = config.extra.eas.build.experimental.ios.appExtensions[configIndex];
appClipConfig.entitlements = {
...appClipConfig.entitlements,
...(0, getAppClipEntitlements_1.getAppClipEntitlements)(config.ios, {
appleSignin,
applePayMerchantIds,
// groupIdentifier, // Throws an error in EAS
pushNotifications,
}),
};
}
// Entitlements
config.ios = {
...config.ios,
entitlements: {
// ...addApplicationGroupsEntitlement(
// config.ios?.entitlements ?? {},
// groupIdentifier,
// ),
...config.ios?.entitlements,
"com.apple.developer.associated-appclip-app-identifiers": [
`$(AppIdentifierPrefix)${bundleIdentifier}`,
],
},
};
return config;
};
exports.withConfig = withConfig;
8 changes: 8 additions & 0 deletions plugin/build/withEntitlements.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { type ConfigPlugin } from "expo/config-plugins";
export declare const withEntitlements: ConfigPlugin<{
targetName: string;
groupIdentifier: string;
appleSignin: boolean;
applePayMerchantIds: string[];
pushNotifications: boolean;
}>;
30 changes: 30 additions & 0 deletions plugin/build/withEntitlements.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.withEntitlements = void 0;
const plist_1 = __importDefault(require("@expo/plist"));
const config_plugins_1 = require("expo/config-plugins");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const getAppClipEntitlements_1 = require("./lib/getAppClipEntitlements");
const withEntitlements = (config, { targetName, groupIdentifier, appleSignin, applePayMerchantIds, pushNotifications }) => {
return (0, config_plugins_1.withInfoPlist)(config, (config) => {
const targetPath = node_path_1.default.join(config.modRequest.platformProjectRoot, targetName);
const filePath = node_path_1.default.join(targetPath, `${targetName}.entitlements`);
if (config.ios === undefined) {
throw new Error("Missing iOS config");
}
const appClipEntitlements = (0, getAppClipEntitlements_1.getAppClipEntitlements)(config.ios, {
groupIdentifier,
appleSignin,
applePayMerchantIds,
pushNotifications,
});
node_fs_1.default.mkdirSync(node_path_1.default.dirname(filePath), { recursive: true });
node_fs_1.default.writeFileSync(filePath, plist_1.default.build(appClipEntitlements));
return config;
});
};
exports.withEntitlements = withEntitlements;
7 changes: 7 additions & 0 deletions plugin/build/withPlist.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { type ConfigPlugin } from "expo/config-plugins";
export declare const withPlist: ConfigPlugin<{
targetName: string;
deploymentTarget: string;
requestEphemeralUserNotification: boolean;
requestLocationConfirmation: boolean;
}>;
79 changes: 79 additions & 0 deletions plugin/build/withPlist.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.withPlist = void 0;
const plist_1 = __importDefault(require("@expo/plist"));
const config_plugins_1 = require("expo/config-plugins");
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const getAppClipIcon_1 = require("./lib/getAppClipIcon");
/**
* List of keys that are not allowed in the Info.plist due to not being supported in App Clip. Add to this array as new build issues arise.
*/
const DisallowedPlistKeys = [
"UIBackgroundModes",
];
const withPlist = (config, { targetName, deploymentTarget, requestEphemeralUserNotification, requestLocationConfirmation, }) => {
return (0, config_plugins_1.withInfoPlist)(config, (config) => {
const targetPath = node_path_1.default.join(config.modRequest.platformProjectRoot, targetName);
// Info.plist
const filePath = node_path_1.default.join(targetPath, "Info.plist");
const infoPlist = {
NSAppClip: {
NSAppClipRequestEphemeralUserNotification: requestEphemeralUserNotification,
NSAppClipRequestLocationConfirmation: requestLocationConfirmation,
},
NSAppTransportSecurity: {
NSAllowsArbitraryLoads: config.developmentClient,
NSExceptionDomains: {
localhost: {
NSExceptionAllowsInsecureHTTPLoads: config.developmentClient,
},
},
NSAllowsLocalNetworking: config.developmentClient,
},
CFBundleName: "$(PRODUCT_MODULE_NAME)",
CFBundleDisplayName: "$(PRODUCT_NAME)",
CFBundleIdentifier: "$(PRODUCT_BUNDLE_IDENTIFIER)",
CFBundleVersion: "$(CURRENT_PROJECT_VERSION)",
CFBundleExecutable: "$(EXECUTABLE_NAME)",
CFBundlePackageType: "$(PRODUCT_BUNDLE_PACKAGE_TYPE)",
CFBundleShortVersionString: config.version,
UIViewControllerBasedStatusBarAppearance: "NO",
UILaunchStoryboardName: "SplashScreen",
UIRequiresFullScreen: true,
MinimumOSVersion: deploymentTarget,
};
if (config.ios?.infoPlist) {
for (const key of Object.keys(config.ios?.infoPlist)) {
if (config.ios?.infoPlist && !DisallowedPlistKeys.some((disallowedKey) => disallowedKey === key)) {
infoPlist[key] = config.ios.infoPlist[key];
}
}
}
infoPlist.CFBundleName = "$(PRODUCT_MODULE_NAME)";
const appClipIcon = (0, getAppClipIcon_1.getAppClipIcon)(config);
if (appClipIcon) {
infoPlist.CFBundleIconName = appClipIcon.name;
}
node_fs_1.default.mkdirSync(node_path_1.default.dirname(filePath), {
recursive: true,
});
node_fs_1.default.writeFileSync(filePath, plist_1.default.build(infoPlist));
// Expo.plist
const expoPlistFilePath = node_path_1.default.join(targetPath, "Supporting/Expo.plist");
const expoPlist = {
EXUpdatesRuntimeVersion: "exposdk:51.0.0", // TODO
// EXUpdatesURL: "", // TODO
EXUpdatesEnabled: false,
};
node_fs_1.default.mkdirSync(node_path_1.default.dirname(expoPlistFilePath), {
recursive: true,
});
node_fs_1.default.writeFileSync(expoPlistFilePath, plist_1.default.build(expoPlist));
return config;
});
};
exports.withPlist = withPlist;
Loading
Loading