UNPKG

@videosdk.live/expo-ios-screen-share

Version:
440 lines (439 loc) 20.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const config_plugins_1 = require("@expo/config-plugins"); const plist_1 = __importDefault(require("@expo/plist")); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const utils_1 = require("./utils"); const getBundleId = (config) => { const expoBundleId = config.ios?.bundleIdentifier; if (expoBundleId) { return expoBundleId; } // Fallback to reading from Info.plist try { const iosRootPath = config.modRequest?.platformProjectRoot; if (iosRootPath) { const projectName = config.modRequest?.projectName; const infoPlistPath = path.join(iosRootPath, projectName, 'Info.plist'); if (fs.existsSync(infoPlistPath)) { const infoPlistContent = fs.readFileSync(infoPlistPath, 'utf8'); const infoPlist = plist_1.default.parse(infoPlistContent); if (infoPlist.CFBundleIdentifier) { return infoPlist.CFBundleIdentifier; } } } } catch (error) { console.warn('⚠️ Could not read bundle ID from Info.plist:', error); } // Final fallback - try to get from Xcode project try { const iosRootPath = config.modRequest?.platformProjectRoot; if (iosRootPath) { const projectPath = path.join(iosRootPath, `${config.modRequest?.projectName}.xcodeproj`, 'project.pbxproj'); if (fs.existsSync(projectPath)) { const projectContent = fs.readFileSync(projectPath, 'utf8'); const bundleIdMatch = projectContent.match(/PRODUCT_BUNDLE_IDENTIFIER = ([^;]+);/); if (bundleIdMatch) { return bundleIdMatch[1].trim(); } } } } catch (error) { console.warn('⚠️ Could not read bundle ID from Xcode project:', error); } throw new Error('❌ Could not determine bundle ID. Please ensure ios.bundleIdentifier is set in app.json or Info.plist exists.'); }; const withIosBroadcastExtension = (config, props) => { // Get bundle ID automatically const bundleId = getBundleId(config); const extensionName = props.extensionName || 'broadcast'; config = withBroadcastExtensionHandler(config, { ...props, bundleId, extensionName }); config = withBroadcastExtensionPlist(config, { ...props, bundleId, extensionName }); config = withBroadcastExtensionXcodeTarget(config, { ...props, bundleId, extensionName }); config = withVideosdkRPKFiles(config, { ...props, bundleId, extensionName }); return config; }; const withBroadcastExtensionHandler = (config, props) => { return (0, config_plugins_1.withDangerousMod)(config, [ 'ios', async (config) => { const extensionRootPath = path.join(config.modRequest.platformProjectRoot, props.extensionName); await fs.promises.mkdir(extensionRootPath, { recursive: true }); // Copy SampleHandler.swift and update app group identifier const sampleHandlerContent = await fs.promises.readFile(path.join(__dirname, 'static', 'SampleHandler.swift'), 'utf8'); const updatedContent = sampleHandlerContent.replace('group.com.anonymous.videosdkexpoapp.appgroup', `group.${props.bundleId}.appgroup`); await fs.promises.writeFile(path.join(extensionRootPath, 'SampleHandler.swift'), updatedContent); // Copy other static files const staticFiles = ['Atomic.swift', 'DarwinNotificationCenter.swift', 'SampleUploader.swift', 'SocketConnection.swift']; for (const file of staticFiles) { await fs.promises.copyFile(path.join(__dirname, 'static', file), path.join(extensionRootPath, file)); } // Create broadcast extension entitlements file const entitlementsContent = `<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.security.application-groups</key> <array> <string>group.${props.bundleId}.appgroup</string> </array> </dict> </plist>`; await fs.promises.writeFile(path.join(extensionRootPath, `${props.extensionName}.entitlements`), entitlementsContent); return config; }, ]); }; const withBroadcastExtensionPlist = (config, props) => { return (0, config_plugins_1.withDangerousMod)(config, [ 'ios', async (config) => { const extensionRootPath = path.join(config.modRequest.platformProjectRoot, props.extensionName); const extensionPlistPath = path.join(extensionRootPath, 'Info.plist'); const extensionPlist = { NSExtension: { NSExtensionPointIdentifier: 'com.apple.broadcast-services-upload', NSExtensionPrincipalClass: '$(PRODUCT_MODULE_NAME).SampleHandler', RPBroadcastProcessMode: 'RPBroadcastProcessModeSampleBuffer', }, }; await fs.promises.mkdir(path.dirname(extensionPlistPath), { recursive: true, }); await fs.promises.writeFile(extensionPlistPath, plist_1.default.build(extensionPlist)); return config; }, ]); }; const withBroadcastExtensionXcodeTarget = (config, props) => { return (0, config_plugins_1.withXcodeProject)(config, async (config) => { const appName = config.modRequest.projectName; const extensionBundleIdentifier = `${props.bundleId}.${props.extensionName}`; const currentProjectVersion = config.ios.buildNumber || '1'; const marketingVersion = config.version; await addBroadcastExtensionXcodeTarget(config.modResults, { appName, extensionName: props.extensionName, extensionBundleIdentifier, currentProjectVersion, marketingVersion, appleTeamId: props.appleTeamId, }); return config; }); }; const addBroadcastExtensionXcodeTarget = async (proj, { appName, extensionName, extensionBundleIdentifier, currentProjectVersion, marketingVersion, appleTeamId, }) => { if (proj.getFirstProject().firstProject.targets?.length > 1) return; const targetUuid = proj.generateUuid(); const groupName = 'Embed App Extensions'; const xCConfigurationList = addXCConfigurationList(proj, { extensionBundleIdentifier, currentProjectVersion, marketingVersion, extensionName, appName, appleTeamId, }); const productFile = addProductFile(proj, extensionName, groupName); const target = addToPbxNativeTargetSection(proj, { extensionName, targetUuid, productFile, xCConfigurationList, }); addToPbxProjectSection(proj, target); addTargetDependency(proj, target); const frameworkFile = proj.addFramework('ReplayKit.framework', { target: target.uuid, link: false, }); const frameworkPath = frameworkFile.path; addBuildPhases(proj, { groupName, productFile, targetUuid, frameworkPath, }, extensionName); addPbxGroup(proj, productFile, extensionName); }; const withVideosdkRPKFiles = (config, props) => { // Step 1: Copy .m and .swift files config = (0, config_plugins_1.withDangerousMod)(config, [ 'ios', async (config) => { const iosRootPath = config.modRequest.platformProjectRoot; const projectName = config.modRequest.projectName; const appPath = path.join(iosRootPath, projectName); await fs.promises.mkdir(appPath, { recursive: true }); const files = ['VideosdkRPK.m', 'VideosdkRPK.swift']; for (const file of files) { const src = path.resolve(__dirname, 'static', file); const dest = path.join(appPath, file); await fs.promises.copyFile(src, dest); console.log(`✅ Copied ${file}${dest}`); } return config; }, ]); // Step 2: Update main app's Info.plist with extension configuration config = (0, config_plugins_1.withDangerousMod)(config, [ 'ios', async (config) => { const iosRootPath = config.modRequest.platformProjectRoot; const projectName = config.modRequest.projectName; const infoPlistPath = path.join(iosRootPath, projectName, 'Info.plist'); if (fs.existsSync(infoPlistPath)) { const infoPlistContent = fs.readFileSync(infoPlistPath, 'utf8'); const infoPlist = plist_1.default.parse(infoPlistContent); // Update or add extension configuration infoPlist.RTCAppGroupIdentifier = `group.${props.bundleId}.appgroup`; infoPlist.RTCScreenSharingExtension = `${props.bundleId}.${props.extensionName}`; infoPlist.RTCScreenSharingExtensionName = `${props.bundleId}.${props.extensionName}`; fs.writeFileSync(infoPlistPath, plist_1.default.build(infoPlist)); } return config; }, ]); // Step 3: Add files to PBXGroup and PBXBuildFile sections config = (0, config_plugins_1.withXcodeProject)(config, async (config) => { const project = config.modResults; const groupName = config_plugins_1.IOSConfig.XcodeUtils.getProjectName(config.modRequest.projectRoot); const mainTarget = project.getFirstTarget(); if (!mainTarget) throw new Error('❌ Main target not found in Xcode project'); const allGroups = project.hash.project.objects.PBXGroup; const groupUUID = Object.entries(allGroups).find(([_, group]) => group.name === groupName || group.path === groupName)?.[0]; if (!groupUUID) throw new Error(`❌ Could not find group "${groupName}" in Xcode project`); const fileNames = ['VideosdkRPK.swift', 'VideosdkRPK.m']; for (const fileName of fileNames) { const filePath = `${groupName}/${fileName}`; if (project.hasFile(filePath)) { continue; } const file = project.addSourceFile(filePath, { target: mainTarget.uuid }, groupUUID); if (file) { console.log(`✅ Registered ${fileName} in PBXBuildFile and Sources build phase`); } else { console.warn(`⚠️ Failed to add ${fileName} to project`); } } return config; }); // Step 4: Patch or create Objective-C bridging header const BRIDGING_HEADER_IMPORT = '#import "React/RCTEventEmitter.h"'; config = (0, config_plugins_1.withDangerousMod)(config, ['ios', (config) => { const projectRoot = config.modRequest.projectRoot; const appName = config_plugins_1.IOSConfig.XcodeUtils.getProjectName(projectRoot); const iosAppDir = path.join(projectRoot, 'ios', appName); const bridgingHeaderFile = `${appName}-Bridging-Header.h`; const bridgingHeaderPath = path.join(iosAppDir, bridgingHeaderFile); if (!fs.existsSync(bridgingHeaderPath)) { fs.writeFileSync(bridgingHeaderPath, `// ${bridgingHeaderFile}\n${BRIDGING_HEADER_IMPORT}\n`, 'utf8'); } else { let content = fs.readFileSync(bridgingHeaderPath, 'utf8'); if (!content.includes(BRIDGING_HEADER_IMPORT)) { content += `\n${BRIDGING_HEADER_IMPORT}\n`; fs.writeFileSync(bridgingHeaderPath, content, 'utf8'); } } return config; }]); // Step 5: Set bridging header path in build settings config = (0, config_plugins_1.withXcodeProject)(config, (config) => { const project = config.modResults; const appName = config_plugins_1.IOSConfig.XcodeUtils.getProjectName(config.modRequest.projectRoot); const mainTarget = project.getFirstTarget(); const bridgingHeaderRelativePath = `${appName}/${appName}-Bridging-Header.h`; project.updateBuildProperty('SWIFT_OBJC_BRIDGING_HEADER', bridgingHeaderRelativePath, mainTarget.uuid); return config; }); return config; }; const addXCConfigurationList = (proj, { extensionBundleIdentifier, currentProjectVersion, marketingVersion, extensionName, appName, appleTeamId, }) => { const commonBuildSettings = { CLANG_ANALYZER_NONNULL: 'YES', CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION: 'YES_AGGRESSIVE', CLANG_CXX_LANGUAGE_STANDARD: (0, utils_1.quoted)('gnu++17'), CLANG_ENABLE_OBJC_WEAK: 'YES', CLANG_WARN_DOCUMENTATION_COMMENTS: 'YES', CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER: 'YES', CLANG_WARN_UNGUARDED_AVAILABILITY: 'YES_AGGRESSIVE', CODE_SIGN_ENTITLEMENTS: `${extensionName}/${extensionName}.entitlements`, CODE_SIGN_STYLE: 'Automatic', CURRENT_PROJECT_VERSION: currentProjectVersion, DEVELOPMENT_TEAM: appleTeamId, GCC_C_LANGUAGE_STANDARD: 'gnu11', GENERATE_INFOPLIST_FILE: 'YES', INFOPLIST_FILE: `${extensionName}/Info.plist`, INFOPLIST_KEY_CFBundleDisplayName: `${extensionName}`, INFOPLIST_KEY_NSHumanReadableCopyright: (0, utils_1.quoted)(''), IPHONEOS_DEPLOYMENT_TARGET: '14.0', LD_RUNPATH_SEARCH_PATHS: (0, utils_1.quoted)('$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks'), MARKETING_VERSION: marketingVersion, MTL_FAST_MATH: 'YES', PRODUCT_BUNDLE_IDENTIFIER: (0, utils_1.quoted)(extensionBundleIdentifier), PRODUCT_NAME: (0, utils_1.quoted)('$(TARGET_NAME)'), SKIP_INSTALL: 'YES', SWIFT_EMIT_LOC_STRINGS: 'YES', SWIFT_VERSION: '5.0', TARGETED_DEVICE_FAMILY: (0, utils_1.quoted)('1,2'), }; const buildConfigurationsList = [ { name: 'Debug', isa: 'XCBuildConfiguration', buildSettings: { ...commonBuildSettings, DEBUG_INFORMATION_FORMAT: 'dwarf', MTL_ENABLE_DEBUG_INFO: 'INCLUDE_SOURCE', SWIFT_ACTIVE_COMPILATION_CONDITIONS: 'DEBUG', SWIFT_OPTIMIZATION_LEVEL: (0, utils_1.quoted)('-Onone'), }, }, { name: 'Release', isa: 'XCBuildConfiguration', buildSettings: { ...commonBuildSettings, COPY_PHASE_STRIP: 'NO', DEBUG_INFORMATION_FORMAT: (0, utils_1.quoted)('dwarf-with-dsym'), SWIFT_OPTIMIZATION_LEVEL: (0, utils_1.quoted)('-Owholemodule'), }, }, ]; const xCConfigurationList = proj.addXCConfigurationList(buildConfigurationsList, 'Release', `Build configuration list for PBXNativeTarget ${(0, utils_1.quoted)(extensionName)}`); // update other build properties proj.updateBuildProperty('ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES', 'YES', null, proj.getFirstTarget().firstTarget.name); proj.updateBuildProperty('IPHONEOS_DEPLOYMENT_TARGET', '14.0'); return xCConfigurationList; }; const addProductFile = (proj, extensionName, groupName) => { const productFile = { basename: `${extensionName}.appex`, fileRef: proj.generateUuid(), uuid: proj.generateUuid(), group: groupName, explicitFileType: 'wrapper.app-extension', settings: { ATTRIBUTES: ['RemoveHeadersOnCopy'], }, includeInIndex: 0, path: `${extensionName}.appex`, sourceTree: 'BUILT_PRODUCTS_DIR', }; proj.addToPbxFileReferenceSection(productFile); proj.addToPbxBuildFileSection(productFile); return productFile; }; const addToPbxNativeTargetSection = (proj, { extensionName, targetUuid, productFile, xCConfigurationList, }) => { const target = { uuid: targetUuid, pbxNativeTarget: { isa: 'PBXNativeTarget', buildConfigurationList: xCConfigurationList.uuid, buildPhases: [], buildRules: [], dependencies: [], name: extensionName, productName: extensionName, productReference: productFile.fileRef, productType: (0, utils_1.quoted)('com.apple.product-type.app-extension'), }, }; proj.addToPbxNativeTargetSection(target); return target; }; const addToPbxProjectSection = (proj, target) => { proj.addToPbxProjectSection(target); // Add target attributes to project section if (!proj.pbxProjectSection()[proj.getFirstProject().uuid].attributes .TargetAttributes) { proj.pbxProjectSection()[proj.getFirstProject().uuid].attributes.TargetAttributes = {}; } proj.pbxProjectSection()[proj.getFirstProject().uuid].attributes.LastSwiftUpdateCheck = 1340; proj.pbxProjectSection()[proj.getFirstProject().uuid].attributes.TargetAttributes[target.uuid] = { CreatedOnToolsVersion: '13.4.1', ProvisioningStyle: 'Automatic', }; }; const addTargetDependency = (proj, target) => { if (!proj.hash.project.objects['PBXTargetDependency']) { proj.hash.project.objects['PBXTargetDependency'] = {}; } if (!proj.hash.project.objects['PBXContainerItemProxy']) { proj.hash.project.objects['PBXContainerItemProxy'] = {}; } proj.addTargetDependency(proj.getFirstTarget().uuid, [target.uuid]); }; const addBuildPhases = (proj, { groupName, productFile, targetUuid, frameworkPath }, extensionName) => { const buildPath = (0, utils_1.quoted)(''); // Sources build phase const { uuid: sourcesBuildPhaseUuid } = proj.addBuildPhase([`SampleHandler.swift`], 'PBXSourcesBuildPhase', 'Sources', targetUuid, 'app_extension', buildPath); // Copy files build phase const { uuid: copyFilesBuildPhaseUuid } = proj.addBuildPhase([productFile.path], 'PBXCopyFilesBuildPhase', groupName, proj.getFirstTarget().uuid, 'app_extension', buildPath); // Frameworks build phase const { uuid: frameworksBuildPhaseUuid } = proj.addBuildPhase([frameworkPath], 'PBXFrameworksBuildPhase', 'Frameworks', targetUuid, 'app_extension', buildPath); // Resources build phase const { uuid: resourcesBuildPhaseUuid } = proj.addBuildPhase([], 'PBXResourcesBuildPhase', 'Resources', targetUuid, 'app_extension', buildPath); }; const addPbxGroup = (proj, productFile, extensionName) => { // Add PBX group const { uuid: pbxGroupUuid } = proj.addPbxGroup(['SampleHandler.swift', 'Info.plist'], extensionName, extensionName); // Add PBXGroup to top level group const groups = proj.hash.project.objects['PBXGroup']; if (pbxGroupUuid) { Object.keys(groups).forEach(function (key) { if (groups[key].name === undefined && groups[key].path === undefined) { proj.addToPbxGroup(pbxGroupUuid, key); } else if (groups[key].name === 'Products') { proj.addToPbxGroup(productFile, key); } }); } }; exports.default = withIosBroadcastExtension;