Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expo Plugin #563

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
8 changes: 6 additions & 2 deletions packages/react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
"release": "release-it",
"example": "yarn --cwd example",
"pods": "cd example/ios && pod install",
"bootstrap": "rm -rf node_modules && rm -rf example/node_modules && yarn && yarn example && yarn pods"
"bootstrap": "rm -rf node_modules && rm -rf example/node_modules && yarn && yarn example && yarn pods",
"plugin:build": "expo-module build",
"plugin:clean": "expo-module clean"
},
"main": "lib/commonjs/index",
"module": "lib/module/index",
Expand All @@ -58,6 +60,8 @@
"eslint": "^7.32.0",
"eslint-config-prettier": "^7.0.0",
"eslint-plugin-prettier": "^3.1.3",
"expo": "^52.0.4",
"expo-module-scripts": "^4.0.2",
"jest": "^26.6.3",
"pod-install": "^0.1.0",
"prettier": "^2.0.5",
Expand Down Expand Up @@ -119,4 +123,4 @@
]
]
}
}
}
205 changes: 205 additions & 0 deletions packages/react-native/plugin/src/addExtension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import * as path from "path";
import type { XcodeProject } from "expo/config-plugins";

const isaXCBuildConfiguration = "XCBuildConfiguration";
const pbxTargetDependency = "PBXTargetDependency";
const pbxContainerItemProxy = "PBXContainerItemProxy";

type AddExtensionProps = {
project: XcodeProject;
targetName: string;
bundleIdentifier: string;
};

export function addExtension({
project,
targetName,
bundleIdentifier,
}: AddExtensionProps) {
// Check if target already exists, if so, cancel

const existing = project.hash.project.objects[isaXCBuildConfiguration];

for (const [, config] of Object.entries(existing)) {
if (typeof config === "string") continue;

if (
(config as any).buildSettings.PRODUCT_BUNDLE_IDENTIFIER &&
(config as any).buildSettings.PRODUCT_BUNDLE_IDENTIFIER ===
bundleIdentifier
) {
return false;
}
}

const targetUuid = project.generateUuid();

const PRODUCT_BUNDLE_IDENTIFIER = bundleIdentifier;
const INFOPLIST_FILE = `${targetName}/Info.plist`;
const CODE_SIGN_ENTITLEMENTS = `${targetName}/${targetName}.entitlements`;

// Create Build Configurations

const commonBuildSettings = {
CODE_SIGN_ENTITLEMENTS,
CODE_SIGN_STYLE: "Automatic",
DEBUG_INFORMATION_FORMAT: "dwarf",
GCC_C_LANGUAGE_STANDARD: "gnu11",
INFOPLIST_FILE,
PRODUCT_BUNDLE_IDENTIFIER,
PRODUCT_NAME: `"$(TARGET_NAME)"`,
SKIP_INSTALL: "YES",
SWIFT_VERSION: "5.0", // TODO: Use main target version
TARGETED_DEVICE_FAMILY: `"1,2"`,
};

const buildConfigurationsList = [
{
name: "Debug",
isa: isaXCBuildConfiguration,
buildSettings: {
...commonBuildSettings,
MTL_ENABLE_DEBUG_INFO: "INCLUDE_SOURCE",
},
},
{
name: "Release",
isa: isaXCBuildConfiguration,
buildSettings: {
...commonBuildSettings,
COPY_PHASE_STRIP: "NO",
},
},
];

const buildConfigurations = project.addXCConfigurationList(
buildConfigurationsList,
"Release",
`Build configuration list for PBXNativeTarget "${targetName}"`,
);

// Create Product

const productName = targetName;
const productType = "com.apple.product-type.app-extension";
const productFileType = '"wrapper.app-extension"';
const productFile = project.addProductFile(productName, {
group: "Embed Foundation Extensions",
target: targetUuid,
explicitFileType: productFileType,
});

productFile.settings = productFile.settings || {};
productFile.settings.ATTRIBUTES = ["RemoveHeadersOnCopy"];

project.addToPbxBuildFileSection(productFile);

const strippedTargetName = path.basename(targetName, ".appex").trim();
const quotedTargetName = `"${strippedTargetName}"`;

// Create Target
const target = {
uuid: targetUuid,
pbxNativeTarget: {
isa: "PBXNativeTarget",
name: strippedTargetName,
productName: quotedTargetName,
productReference: productFile.fileRef,
productType: `"${productType}"`,
buildConfigurationList: buildConfigurations.uuid,
buildPhases: [],
buildRules: [],
dependencies: [],
},
};

project.addToPbxNativeTargetSection(target);

// Add Extension files
const extensionGroup = project.addPbxGroup(
[],
targetName,
targetName,
'"<group>"',
);
const notificationServiceSwiftFile = project.addFile(
"BreezNotificationService.swift",
extensionGroup.uuid,
{
lastKnownFileType: "sourcecode.swift",
defaultEncoding: 4,
sourceTree: '"<group>"',
},
);

notificationServiceSwiftFile.target = targetUuid;
notificationServiceSwiftFile.uuid = project.generateUuid();

project.addToPbxBuildFileSection(notificationServiceSwiftFile);
const extensionSourcesBuildPhase = project.addBuildPhase(
[],
"PBXSourcesBuildPhase",
"Sources",
targetUuid,
);
extensionSourcesBuildPhase.buildPhase.files.push(
notificationServiceSwiftFile.uuid,
);

const mainGroupUUID = project.getFirstProject().firstProject.mainGroup;
const mainGroup = project.getPBXGroupByKey(mainGroupUUID);
mainGroup.children.push({ value: extensionGroup.uuid, comment: targetName });

// Create Build Phases
const extensionCopyBuildPhase = project.addBuildPhase(
[],
"PBXCopyFilesBuildPhase",
"Embed Foundation Extension",
project.getFirstTarget().uuid,
// targetType,
"app_extension",
);

extensionCopyBuildPhase.buildPhase.dstSubfolderSpec = 13;

addToPbxCopyfilesBuildPhase(
project,
productFile,
"Embed Foundation Extension",
);
project.addBuildPhase([], "PBXResourcesBuildPhase", targetName, targetUuid);
project.addToPbxProjectSection(target);

if (!project.hash.project.objects[pbxTargetDependency]) {
project.hash.project.objects[pbxTargetDependency] = {};
}
if (!project.hash.project.objects[pbxContainerItemProxy]) {
project.hash.project.objects[pbxContainerItemProxy] = {};
}

project.addTargetDependency(project.getFirstTarget().uuid, [target.uuid]);

return target;
}

function pbxBuildPhaseObj(file: any) {
const obj = Object.create(null);

obj.value = file.uuid;
obj.comment = `${file.basename} in ${file.group}`;

return obj;
}

function addToPbxCopyfilesBuildPhase(
project: XcodeProject,
file: any,
name: string,
) {
const sources = project.buildPhaseObject(
"PBXCopyFilesBuildPhase",
name || "Copy Files",
file.target,
);
sources.files.push(pbxBuildPhaseObj(file));
}
32 changes: 32 additions & 0 deletions packages/react-native/plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { ExpoConfig } from 'expo/config';
import { createRunOncePlugin } from 'expo/config-plugins';
import { warnOnce } from './utils';
import { withNotificationServiceExtension } from './withBreezIOS';
const pkg = require("@breeztech/react-native-breez-sdk-liquid");

type PluginProps = {
apiKey: string;
keyService?: string;
mnemonicKeyName?: string;
}

function withBreezPlugin(config: ExpoConfig, props?: PluginProps): ExpoConfig {
const apiKey = props?.apiKey

if(apiKey === undefined) {
warnOnce("API Key not set.");
return config;
}

const keyService = props?.keyService ?? "app:no-auth"; // This is the default name in expo-secure-store
const mnemonicKeyName = props?.keyService ?? "mnemonic";

// iOS Configuration
config = withNotificationServiceExtension(config, { apiKey, keyService, mnemonicKeyName});

// TODO: Android Configuration

return config;
}

export default createRunOncePlugin(withBreezPlugin, pkg.name, pkg.version)
21 changes: 21 additions & 0 deletions packages/react-native/plugin/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const warningMap = new Map<string, boolean>();

export function logPrefix(): string {
return `› ${bold('[@breeztech/react-native/expo]')}`;
}

export function warnOnce(message: string): void {
if (!warningMap.has(message)) {
warningMap.set(message, true);
// eslint-disable-next-line no-console
console.warn(yellow(`${logPrefix()} ${message}`));
}
}

export function yellow(message: string): string {
return `\x1b[33m${message}\x1b[0m`;
}

export function bold(message: string): string {
return `\x1b[1m${message}\x1b[22m`;
}
Loading