Skip to content

Commit

Permalink
feat: handle custom ios identifiers
Browse files Browse the repository at this point in the history
  • Loading branch information
achorein committed Sep 23, 2024
1 parent a421289 commit 7d50104
Show file tree
Hide file tree
Showing 10 changed files with 70 additions and 41 deletions.
9 changes: 6 additions & 3 deletions ios/ExpoShareIntentModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,12 @@ public class ExpoShareIntentModule: Module {
private var latestText: String? = nil

private func handleUrl(url: URL?) -> String? {
let appDomain = Bundle.main.bundleIdentifier!
let appGroupIdentifier =
Bundle.main.object(forInfoDictionaryKey: "com.apple.security.application-groups")
.first
as? String
if let url = url {
let userDefaults = UserDefaults(suiteName: "group.\(appDomain)")
let userDefaults = UserDefaults(suiteName: self.appGroupIdentifier)
if url.fragment == "media" {
if let key = url.host?.components(separatedBy: "=").last {
if let json = userDefaults?.object(forKey: key) as? Data {
Expand Down Expand Up @@ -141,7 +144,7 @@ public class ExpoShareIntentModule: Module {
"onError",
[
"value":
"invalid group name. Please check your share extention bundle name is same as `group.\(appDomain)`"
"invalid group name. Please check your share extention bundle name is same as `\(self!.appGroupIdentifier)`"
])
return "error"
}
Expand Down
2 changes: 1 addition & 1 deletion plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const withShareMenu: ConfigPlugin<Parameters> = createRunOncePlugin(
// IOS
...(!params.disableIOS
? [
withAppEntitlements,
() => withAppEntitlements(config, params),
() => withShareExtensionConfig(config, params),
() => withShareExtensionXcodeTarget(config, params),
]
Expand Down
20 changes: 10 additions & 10 deletions plugin/src/ios/ShareExtensionViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Social
import UIKit

class ShareViewController: UIViewController {
let hostAppBundleIdentifier = "<APPIDENTIFIER>"
let hostAppGroupIdentifier = "<GROUPIDENTIFIER>"
let shareProtocol = "<SCHEME>"
let sharedKey = "<SCHEME>ShareKey"
var sharedMedia: [SharedMediaFile] = []
Expand Down Expand Up @@ -64,7 +64,7 @@ class ShareViewController: UIViewController {
self?.sharedText.append(item)
// If this is the last item, save sharedText in userDefaults and redirect to host app
if index == (content.attachments?.count)! - 1 {
let userDefaults = UserDefaults(suiteName: "group.\(self!.hostAppBundleIdentifier)")
let userDefaults = UserDefaults(suiteName: self!.hostAppGroupIdentifier)
userDefaults?.set(self!.sharedText, forKey: self!.sharedKey)
userDefaults?.synchronize()
self?.redirectToHostApp(type: .text)
Expand All @@ -88,7 +88,7 @@ class ShareViewController: UIViewController {
self!.sharedText.append(item.absoluteString)
// If this is the last item, save sharedText in userDefaults and redirect to host app
if index == (content.attachments?.count)! - 1 {
let userDefaults = UserDefaults(suiteName: "group.\(self!.hostAppBundleIdentifier)")
let userDefaults = UserDefaults(suiteName: self!.hostAppGroupIdentifier)
userDefaults?.set(self!.sharedText, forKey: self!.sharedKey)
userDefaults?.synchronize()
self!.redirectToHostApp(type: .weburl)
Expand Down Expand Up @@ -146,7 +146,7 @@ class ShareViewController: UIViewController {
let newName = "\(UUID().uuidString).\(fileExtension)"
let newPath = FileManager.default
.containerURL(
forSecurityApplicationGroupIdentifier: "group.\(self!.hostAppBundleIdentifier)")!
forSecurityApplicationGroupIdentifier: self!.hostAppGroupIdentifier)!
.appendingPathComponent(newName)
let copied = self!.copyFile(at: url!, to: newPath)
if copied {
Expand All @@ -159,7 +159,7 @@ class ShareViewController: UIViewController {

// If this is the last item, save imagesData in userDefaults and redirect to host app
if index == (content.attachments?.count)! - 1 {
let userDefaults = UserDefaults(suiteName: "group.\(self!.hostAppBundleIdentifier)")
let userDefaults = UserDefaults(suiteName: self!.hostAppGroupIdentifier)
userDefaults?.set(self!.toData(data: self!.sharedMedia), forKey: self!.sharedKey)
userDefaults?.synchronize()
self!.redirectToHostApp(type: .media)
Expand Down Expand Up @@ -205,7 +205,7 @@ class ShareViewController: UIViewController {
let newName = "\(UUID().uuidString).\(fileExtension)"
let newPath = FileManager.default
.containerURL(
forSecurityApplicationGroupIdentifier: "group.\(self!.hostAppBundleIdentifier)")!
forSecurityApplicationGroupIdentifier: self!.hostAppGroupIdentifier)!
.appendingPathComponent(newName)
let copied = self!.copyFile(at: url, to: newPath)
if copied {
Expand All @@ -220,7 +220,7 @@ class ShareViewController: UIViewController {

// If this is the last item, save imagesData in userDefaults and redirect to host app
if index == (content.attachments?.count)! - 1 {
let userDefaults = UserDefaults(suiteName: "group.\(self!.hostAppBundleIdentifier)")
let userDefaults = UserDefaults(suiteName: self!.hostAppGroupIdentifier)
userDefaults?.set(self!.toData(data: self!.sharedMedia), forKey: self!.sharedKey)
userDefaults?.synchronize()
self!.redirectToHostApp(type: .media)
Expand Down Expand Up @@ -248,7 +248,7 @@ class ShareViewController: UIViewController {
let newName = "\(UUID().uuidString).\(fileExtension)"
let newPath = FileManager.default
.containerURL(
forSecurityApplicationGroupIdentifier: "group.\(self!.hostAppBundleIdentifier)")!
forSecurityApplicationGroupIdentifier: self!.hostAppGroupIdentifier)!
.appendingPathComponent(newName)
let copied = self!.copyFile(at: url, to: newPath)
if copied {
Expand All @@ -260,7 +260,7 @@ class ShareViewController: UIViewController {
}

if index == (content.attachments?.count)! - 1 {
let userDefaults = UserDefaults(suiteName: "group.\(self!.hostAppBundleIdentifier)")
let userDefaults = UserDefaults(suiteName: self!.hostAppGroupIdentifier)
userDefaults?.set(self!.toData(data: self!.sharedMedia), forKey: self!.sharedKey)
userDefaults?.synchronize()
self!.redirectToHostApp(type: .file)
Expand Down Expand Up @@ -416,7 +416,7 @@ class ShareViewController: UIViewController {
let fileName = Data(url.lastPathComponent.utf8).base64EncodedString().replacingOccurrences(
of: "==", with: "")
let path = FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: "group.\(hostAppBundleIdentifier)")!
.containerURL(forSecurityApplicationGroupIdentifier: self!.hostAppGroupIdentifier)!
.appendingPathComponent("\(fileName).jpg")
return path
}
Expand Down
9 changes: 8 additions & 1 deletion plugin/src/ios/constants.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { Parameters } from "../types";

export const shareExtensionName = "ShareExtension";

export const shareExtensionInfoFileName = `${shareExtensionName}-Info.plist`;
export const shareExtensionEntitlementsFileName = `${shareExtensionName}.entitlements`;
export const shareExtensionStoryBoardFileName = "MainInterface.storyboard";
export const shareExtensionViewControllerFileName = "ShareViewController.swift";

export const getAppGroups = (identifier: string) => [`group.${identifier}`];
export const getShareExtensionName = (parameters?: Parameters) =>
parameters?.iosShareExtensionName || "ShareExtension";

export const getAppGroup = (identifier: string, parameters: Parameters) => {
return parameters.iosAppGroupIdentifier || `group.${identifier}`;
};

export const getShareExtensionBundledIdentifier = (appIdentifier: string) =>
`${appIdentifier}.share-extension`;
13 changes: 9 additions & 4 deletions plugin/src/ios/withIosAppEntitlements.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { ConfigPlugin, withEntitlementsPlist } from "@expo/config-plugins";

import { getAppGroups } from "./constants";
import { getAppGroup } from "./constants";
import { Parameters } from "../types";

export const withAppEntitlements: ConfigPlugin = (config) => {
export const withAppEntitlements: ConfigPlugin<Parameters> = (
config,
parameters,
) => {
return withEntitlementsPlist(config, async (config) => {
const appIdentifier = config.ios?.bundleIdentifier!;
config.modResults["com.apple.security.application-groups"] =
getAppGroups(appIdentifier);
config.modResults["com.apple.security.application-groups"] = [
getAppGroup(appIdentifier, parameters),
];
return config;
});
};
2 changes: 1 addition & 1 deletion plugin/src/ios/withIosShareExtensionConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const withShareExtensionConfig: ConfigPlugin<Parameters> = (
config.extra.eas.build.experimental.ios.appExtensions[extConfigIndex];
extConfig.entitlements = {
...extConfig.entitlements,
...getShareExtensionEntitlements(appIdentifier),
...getShareExtensionEntitlements(appIdentifier, params),
};
} else {
console.debug(`[expo-share-intent] experimental config disabled`);
Expand Down
4 changes: 2 additions & 2 deletions plugin/src/ios/withIosShareExtensionXcodeTarget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ConfigPlugin, withXcodeProject } from "@expo/config-plugins";

import {
getShareExtensionBundledIdentifier,
shareExtensionName,
getShareExtensionName,
} from "./constants";
import {
getPrivacyInfoFilePath,
Expand All @@ -19,7 +19,7 @@ export const withShareExtensionXcodeTarget: ConfigPlugin<Parameters> = (
parameters,
) => {
return withXcodeProject(config, async (config) => {
const extensionName = shareExtensionName;
const extensionName = getShareExtensionName();
const platformProjectRoot = config.modRequest.platformProjectRoot;
const scheme = config.scheme! as string;
const appIdentifier = config.ios?.bundleIdentifier!;
Expand Down
44 changes: 27 additions & 17 deletions plugin/src/ios/writeIosShareExtensionFiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import fs from "node:fs";
import path from "node:path";

import {
shareExtensionName,
getAppGroups,
getShareExtensionName,
getAppGroup,
shareExtensionEntitlementsFileName,
shareExtensionInfoFileName,
shareExtensionStoryBoardFileName,
Expand All @@ -32,8 +32,10 @@ export async function writeShareExtensionFiles(
// ShareExtension.entitlements
const entitlementsFilePath =
getShareExtensionEntitlementsFilePath(platformProjectRoot);
const entitlementsContent =
getShareExtensionEntitlementsContent(appIdentifier);
const entitlementsContent = getShareExtensionEntitlementsContent(
appIdentifier,
parameters,
);
await fs.promises.writeFile(entitlementsFilePath, entitlementsContent);

// PrivacyInfo.xcprivacy
Expand All @@ -52,7 +54,7 @@ export async function writeShareExtensionFiles(
getShareExtensionViewControllerPath(platformProjectRoot);
const viewControllerContent = getShareExtensionViewControllerContent(
scheme,
appIdentifier,
getAppGroup(appIdentifier, parameters),
);
await fs.promises.writeFile(viewControllerFilePath, viewControllerContent);
}
Expand All @@ -63,26 +65,34 @@ export function getShareExtensionEntitlementsFilePath(
) {
return path.join(
platformProjectRoot,
shareExtensionName,
getShareExtensionName(),
shareExtensionEntitlementsFileName,
);
}

export function getShareExtensionEntitlements(appIdentifier: string) {
export function getShareExtensionEntitlements(
appIdentifier: string,
parameters: Parameters,
) {
return {
"com.apple.security.application-groups": getAppGroups(appIdentifier),
"com.apple.security.application-groups": [
getAppGroup(appIdentifier, parameters),
],
};
}

export function getShareExtensionEntitlementsContent(appIdentifier: string) {
return plist.build(getShareExtensionEntitlements(appIdentifier));
export function getShareExtensionEntitlementsContent(
appIdentifier: string,
parameters: Parameters,
) {
return plist.build(getShareExtensionEntitlements(appIdentifier, parameters));
}

//: [root]/ios/ShareExtension/ShareExtension-Info.plist
export function getShareExtensionInfoFilePath(platformProjectRoot: string) {
return path.join(
platformProjectRoot,
shareExtensionName,
getShareExtensionName(),
shareExtensionInfoFileName,
);
}
Expand Down Expand Up @@ -116,7 +126,7 @@ export function getShareExtensionInfoContent(
export function getPrivacyInfoFilePath(platformProjectRoot: string) {
return path.join(
platformProjectRoot,
shareExtensionName,
getShareExtensionName(),
"PrivacyInfo.xcprivacy",
);
}
Expand All @@ -140,7 +150,7 @@ export function getShareExtensionStoryboardFilePath(
) {
return path.join(
platformProjectRoot,
shareExtensionName,
getShareExtensionName(),
shareExtensionStoryBoardFileName,
);
}
Expand Down Expand Up @@ -179,14 +189,14 @@ export function getShareExtensionViewControllerPath(
) {
return path.join(
platformProjectRoot,
shareExtensionName,
getShareExtensionName(),
shareExtensionViewControllerFileName,
);
}

export function getShareExtensionViewControllerContent(
scheme: string,
appIdentifier: string,
groupIdentifier: string,
) {
let updatedScheme = scheme;
if (Array.isArray(scheme)) {
Expand All @@ -196,7 +206,7 @@ export function getShareExtensionViewControllerContent(
updatedScheme = scheme[0];
}
console.debug(
`[expo-share-intent] add ios share extension (scheme:${updatedScheme} appIdentifier:${appIdentifier})`,
`[expo-share-intent] add ios share extension (scheme:${updatedScheme} groupIdentifier:${groupIdentifier})`,
);
if (!updatedScheme) {
throw new Error(
Expand All @@ -211,5 +221,5 @@ export function getShareExtensionViewControllerContent(

return content
.replaceAll("<SCHEME>", updatedScheme)
.replaceAll("<APPIDENTIFIER>", appIdentifier);
.replaceAll("<GROUPIDENTIFIER>", groupIdentifier);
}
2 changes: 2 additions & 0 deletions plugin/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export type CustomParameter = { [key: string]: string };

export type Parameters = {
iosActivationRules?: { [key: string]: number | boolean | string };
iosShareExtensionName?: string;
iosAppGroupIdentifier?: string;
androidMainActivityAttributes?: CustomParameter;
androidIntentFilters?: ("text/*" | "image/*" | "video/*" | "*/*")[];
androidMultiIntentFilters?: ("image/*" | "video/*" | "*/*")[];
Expand Down
6 changes: 4 additions & 2 deletions plugin/src/withCompatibilityChecker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ConfigPlugin, WarningAggregator } from "@expo/config-plugins";

import { getShareExtensionName } from "./ios/constants";
import { Parameters } from "./types";
import packageInfo from "../../package.json";

Expand Down Expand Up @@ -42,11 +43,12 @@ export const withCompatibilityChecker: ConfigPlugin<Parameters> = (

const extraAppExtension =
config.extra?.eas?.build?.experimental?.ios?.appExtensions?.filter(
(appExtension: any) => appExtension.targetName === "ShareExtension",
(appExtension: any) =>
appExtension.targetName === getShareExtensionName(),
);
if (extraAppExtension && extraAppExtension.length > 1) {
throw new Error(
`[${packageInfo.name}] Incompatibility found, you have more than one appExtensions for "ShareExtension" (${extraAppExtension.length}). Please remove all "eas.build.experimental.ios.appExtensions" with targetName "ShareExtension" in your app.json! (see https://github.com/achorein/expo-share-intent?tab=readme-ov-file#ios-extension-target)`,
`[${packageInfo.name}] Incompatibility found, you have more than one appExtensions for "${getShareExtensionName()}" (${extraAppExtension.length}). Please remove all "eas.build.experimental.ios.appExtensions" with targetName "${getShareExtensionName()}" in your app.json! (see https://github.com/achorein/expo-share-intent?tab=readme-ov-file#ios-extension-target)`,
);
}
} else {
Expand Down

0 comments on commit 7d50104

Please sign in to comment.