diff --git a/.gitignore b/.gitignore index 489e37f5..77a1f4d9 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,7 @@ yarn.lock .tmp/ # Build files -build +*/*/server/build # Zip files *.zip diff --git a/shoutem.advertising/app/build/const.js b/shoutem.advertising/app/build/const.js new file mode 100644 index 00000000..8e01afc8 --- /dev/null +++ b/shoutem.advertising/app/build/const.js @@ -0,0 +1,16 @@ +const pack = require('../package.json'); + +// defines scope for the current extension state within the global app's state +function ext(resourceName) { + return resourceName ? `${pack.name}.${resourceName}` : pack.name; +} + +const DEFAULT_ADMOB_APPS = { + IOS: 'ca-app-pub-7090812163729304~6362478856', + ANDROID: 'ca-app-pub-7090812163729304~5646069659' +}; + +module.exports = { + ext, + DEFAULT_ADMOB_APPS, +}; diff --git a/shoutem.advertising/app/build/index.js b/shoutem.advertising/app/build/index.js new file mode 100644 index 00000000..525d86bc --- /dev/null +++ b/shoutem.advertising/app/build/index.js @@ -0,0 +1,7 @@ +const { injectFirebaseSettings } = require('./injectFirebaseSettings'); +const { injectAdMobPlistData } = require('./injectAdMobPlistData') + +exports.preBuild = function preBuild(appConfiguration) { + injectFirebaseSettings(appConfiguration); + injectAdMobPlistData(appConfiguration) +}; diff --git a/shoutem.advertising/app/build/injectAdMobPlistData.js b/shoutem.advertising/app/build/injectAdMobPlistData.js new file mode 100644 index 00000000..54b83114 --- /dev/null +++ b/shoutem.advertising/app/build/injectAdMobPlistData.js @@ -0,0 +1,62 @@ +const _ = require('lodash'); +const fs = require('fs-extra'); +const plist = require('plist'); +const { ext, DEFAULT_ADMOB_APPS } = require('./const'); + +function parsePlist(plistPath) { + let plistResult = {}; + + if (fs.existsSync(plistPath)) { + const plistContent = fs.readFileSync(plistPath, 'utf8'); + + try { + plistResult = plist.parse(plistContent); + } catch (e) { + console.error('Unable to parse plist', plistPath); + } + } + + return plistResult; +} + +const getExtension = (appConfiguration, extensionName) => { + const includedResources = _.get(appConfiguration, 'included'); + const extension = _.find(includedResources, { + type: 'shoutem.core.extensions', + id: extensionName, + }); + + return extension; +}; + +const getExtensionSettings = (appConfiguration, extensionName) => { + const extension = getExtension(appConfiguration, extensionName); + return _.get(extension, 'attributes.settings'); +}; + +/** + * Adding GADApplicationIdentifier and SKAdNetworkItems keys per AdMob instructions. + * https://developers.google.com/admob/ios/quick-start#update_your_infoplist + */ +function injectAdMobPlistData(appConfiguration) { + const plistPath = 'ios/Info.plist'; + const currentPlistContents = parsePlist(plistPath); + const extSettings = getExtensionSettings(appConfiguration, ext()); + const iOSAdAppId = _.get(extSettings, 'iOSAdAppId', DEFAULT_ADMOB_APPS.IOS); + + const adMobPlistData = { + GADApplicationIdentifier: iOSAdAppId, + SKAdNetworkItems: [ + { SKAdNetworkIdentifier: 'cstr6suwn9.skadnetwork' } + ] + }; + + const adInfoPlist = Object.assign(currentPlistContents, adMobPlistData); + + fs.writeFileSync(plistPath, plist.build(adInfoPlist)); +} + + +module.exports = { + injectAdMobPlistData, +}; diff --git a/shoutem.advertising/app/build/injectFirebaseSettings.js b/shoutem.advertising/app/build/injectFirebaseSettings.js new file mode 100644 index 00000000..3f73de35 --- /dev/null +++ b/shoutem.advertising/app/build/injectFirebaseSettings.js @@ -0,0 +1,49 @@ +const _ = require('lodash'); +const fs = require('fs-extra'); +const path = require('path'); +const { projectPath } = require('@shoutem/build-tools'); +const { ext, DEFAULT_ADMOB_APPS } = require('./const'); + +const getExtension = (appConfiguration, extensionName) => { + const includedResources = _.get(appConfiguration, 'included'); + const extension = _.find(includedResources, { + type: 'shoutem.core.extensions', + id: extensionName, + }); + + return extension; +}; + +const getExtensionSettings = (appConfiguration, extensionName) => { + const extension = getExtension(appConfiguration, extensionName); + return _.get(extension, 'attributes.settings'); +}; + +function injectFirebaseSettings(appConfiguration) { + const extSettings = getExtensionSettings(appConfiguration, ext()); + const AndroidAdAppId = _.get(extSettings, 'AndroidAdAppId', DEFAULT_ADMOB_APPS.ANDROID); + const iOSAdAppId = _.get(extSettings, 'iOSAdAppId', DEFAULT_ADMOB_APPS.IOS); + const filePath = path.resolve(projectPath, 'firebase.json'); + + try { + fs.ensureFileSync(filePath); + const existingConfig = fs.readJsonSync(filePath, { throws: false }); + const config = { + 'react-native': { + admob_android_app_id: AndroidAdAppId, + admob_ios_app_id: iOSAdAppId, + }, + }; + const actionTaken = _.isEmpty(existingConfig) ? 'Created' : 'Modified'; + const newConfig = existingConfig ? _.merge(existingConfig, config) : config; + fs.writeJsonSync(filePath, newConfig); + + console.log(`[${ext()}] - ${actionTaken} root/firebase.json`); + } catch (error) { + console.log(error); + } +} + +module.exports = { + injectFirebaseSettings, +}; diff --git a/shoutem.agora/app/build/const.js b/shoutem.agora/app/build/const.js new file mode 100644 index 00000000..15f912d6 --- /dev/null +++ b/shoutem.agora/app/build/const.js @@ -0,0 +1,10 @@ +const CAMERA_PERMISSION_HANDLER = + " pod 'Permission-Camera', :path => '../node_modules/react-native-permissions/ios/Camera.podspec'"; + +const MIC_PERMISSION_HANDLER = + "pod 'Permission-Microphone', :path => '../node_modules/react-native-permissions/ios/Microphone.podspec'"; + +module.exports = { + CAMERA_PERMISSION_HANDLER, + MIC_PERMISSION_HANDLER, +}; diff --git a/shoutem.agora/app/build/index.js b/shoutem.agora/app/build/index.js new file mode 100644 index 00000000..47688a19 --- /dev/null +++ b/shoutem.agora/app/build/index.js @@ -0,0 +1,5 @@ +const { injectPermissionHandlerIos } = require('./injectPermissions'); + +exports.preBuild = function preBuild() { + injectPermissionHandlerIos(); +}; diff --git a/shoutem.agora/app/build/injectPermissions.js b/shoutem.agora/app/build/injectPermissions.js new file mode 100644 index 00000000..3214ec05 --- /dev/null +++ b/shoutem.agora/app/build/injectPermissions.js @@ -0,0 +1,35 @@ +const { + ANCHORS, + getPodfileTemplatePath, + inject, + projectPath, +} = require('@shoutem/build-tools'); +const { + CAMERA_PERMISSION_HANDLER, + MIC_PERMISSION_HANDLER, +} = require('./const'); + +/** + * ios/Podfile.template injects required modifications for react-native-permissions package as described + * here: https://github.com/react-native-community/react-native-permissions#ios + * + * This must be run before pod install step + */ + +function injectPermissionHandlerIos() { + const podfileTemplatePath = getPodfileTemplatePath({ cwd: projectPath }); + + inject( + podfileTemplatePath, + ANCHORS.IOS.PODFILE.EXTENSION_DEPENDENCIES, + CAMERA_PERMISSION_HANDLER, + ); + + inject( + podfileTemplatePath, + ANCHORS.IOS.PODFILE.EXTENSION_DEPENDENCIES, + MIC_PERMISSION_HANDLER, + ); +} + +module.exports = { injectPermissionHandlerIos }; diff --git a/shoutem.audio/app/build/add-background-modes.js b/shoutem.audio/app/build/add-background-modes.js new file mode 100644 index 00000000..6a55744b --- /dev/null +++ b/shoutem.audio/app/build/add-background-modes.js @@ -0,0 +1,69 @@ +const _ = require('lodash'); +const fs = require('fs-extra'); +const plist = require('plist'); + +function parsePlist(plistPath) { + let plistResult = {}; + + if (!fs.existsSync(plistPath)) { + console.log('No path found on ' + plistPath); + return null; + } + + const plistContent = fs.readFileSync(plistPath, 'utf8'); + + try { + plistResult = plist.parse(plistContent); + } catch (e) { + console.error('Unable to parse plist', plistPath); + } + + return plistResult; +} + +function hasTrackPlayingShortcut(configuration) { + const hasTrackPlayingShortcut = configuration.included.find(item => { + const isShortcut = item.type === 'shoutem.core.shortcuts'; + + if (!isShortcut) { + return false; + } + + const canonicalName = _.get(item, 'attributes.canonicalName', ''); + + return ( + canonicalName.includes('Radio') || + canonicalName.includes('podcast-shortcut') + ); + }); + + return hasTrackPlayingShortcut; +} + +/** + * If radio or podcast shortcut is present, writes the required background + * mode into extension's Info.plist file + */ +function updateInfoPlist(appConfiguration) { + if (!hasTrackPlayingShortcut(appConfiguration)) { + return; + } + + console.log('[shoutem.audio] - Adding audio background mode to extension Info.plist'); + + const plistPath = 'ios/Info.plist'; + const currentPlistContents = parsePlist(plistPath); + const backgroundModePlistData = { + UIBackgroundModes: [ + 'audio', + ], + }; + + const audioInfoPlist = Object.assign(currentPlistContents, backgroundModePlistData); + + fs.writeFileSync(plistPath, plist.build(audioInfoPlist)); +}; + +module.exports = { + updateInfoPlist, +}; diff --git a/shoutem.audio/app/build/index.js b/shoutem.audio/app/build/index.js new file mode 100644 index 00000000..b080d403 --- /dev/null +++ b/shoutem.audio/app/build/index.js @@ -0,0 +1,7 @@ +const { injectTrackPlayer } = require('./injectTrackPlayer'); +const { updateInfoPlist } = require('./add-background-modes'); + +exports.preBuild = function preBuild(appConfiguration) { + updateInfoPlist(appConfiguration); + injectTrackPlayer(); +}; diff --git a/shoutem.audio/app/build/injectTrackPlayer.js b/shoutem.audio/app/build/injectTrackPlayer.js new file mode 100644 index 00000000..97243f79 --- /dev/null +++ b/shoutem.audio/app/build/injectTrackPlayer.js @@ -0,0 +1,23 @@ +const fs = require('fs-extra'); +const { + getGradlePropertiesPath, + projectPath, +} = require('@shoutem/build-tools'); + +function injectTrackPlayer() { + const searchText = '# org.gradle.jvmargs'; + const replaceText = 'org.gradle.jvmargs'; + + const filePath = getGradlePropertiesPath({ cwd: projectPath }); + const fileContents = fs.readFileSync(filePath, 'utf8'); + + const newFileContents = fileContents.replace(searchText, replaceText); + + fs.writeFileSync(filePath, newFileContents, 'utf8'); + + console.log('[shoutem.audio] - Uncommenting JVM memory arguments in "android/gradle.properties" for `react-native-track-player`'); +} + +module.exports = { + injectTrackPlayer, +}; diff --git a/shoutem.auth/app/build/configureFacebookSdk.js b/shoutem.auth/app/build/configureFacebookSdk.js new file mode 100644 index 00000000..fd70b2f1 --- /dev/null +++ b/shoutem.auth/app/build/configureFacebookSdk.js @@ -0,0 +1,141 @@ +const _ = require('lodash'); +const fs = require('fs-extra'); +const path = require('path'); +const plist = require('plist'); +const { projectPath } = require('@shoutem/build-tools'); + +function parsePlist(plistPath) { + let plistResult = {}; + + if (fs.existsSync(plistPath)) { + const plistContent = fs.readFileSync(plistPath, 'utf8'); + + try { + plistResult = plist.parse(plistContent); + } catch (e) { + console.error('Unable to parse plist', plistPath); + } + } + + return plistResult; +} + +function getFileContents(filePath) { + let fileContents = ''; + + if (fs.existsSync(filePath)) { + try { + fileContents = fs.readFileSync(filePath, { encoding: 'utf8' }); + } catch (err) { + console.log(`${filePath} not found or unreadable!`); + throw new Error(err); + } + } + + return fileContents; +} + +/** + * Resolves ios project settings to be configured for facebook authentication + * If facebook authentication is enabled, writes the required keys to the Info.plist file + */ +const configureFacebookSettingsIos = (facebookSettings) => { + console.log('Configuring Facebook login settings for iOS'); + + const plistPath = 'ios/Info.plist'; + const currentPlistContents = parsePlist(plistPath); + + const appId = _.get(facebookSettings, 'appId'); + const appName = _.get(facebookSettings, 'appName'); + + const facebookPlistData = { + CFBundleURLTypes: [{ + CFBundleURLSchemes: [`fb${appId}`], + }], + FacebookAppID: appId, + FacebookDisplayName: appName, + FacebookAutoLogAppEventsEnabled: false, + LSApplicationQueriesSchemes: [ + 'fbapi', + 'fb-messenger-api', + 'fbauth2', + 'fbshareextension', + ], + }; + + const authInfoPlist = Object.assign(currentPlistContents, facebookPlistData); + + fs.writeFileSync(plistPath, plist.build(authInfoPlist)); +}; + +const getExistingFacebookAppId = (fileContents) => { + const fbAppIdRegex = /([^<]*)<\/string>/; + const result = fileContents.match(fbAppIdRegex); + + return result && result[1]; +}; + +const configureFacebookSettingsAndroid = (facebookSettings) => { + console.log('Configuring Facebook login settings for Android'); + + const stringsXmlFilePath = path.resolve(projectPath, 'android/app/src/main/res/values/strings.xml'); + const stringsXmlContents = getFileContents(stringsXmlFilePath); + + const facebookAppId = _.get(facebookSettings, 'appId'); + const isFacebookAuthEnabled = _.get(facebookSettings, 'enabled', false); + const existingFacebookAppId = getExistingFacebookAppId(stringsXmlContents); + const facebookAppIdDiffers = (facebookAppId !== existingFacebookAppId); + + // fbauth is enabled, appId string is present and matches the current appId: no changes needed + if (isFacebookAuthEnabled && !facebookAppIdDiffers) { + return; + } + + const facebookAppIdString = `${facebookAppId}`; + const existingFacebookAppIdString = `${existingFacebookAppId}`; + + let searchString = ''; + let replaceString = ''; + + // fbauth is enabled, but appIds differ: replace the old appId with the new one + if (isFacebookAuthEnabled && facebookAppIdDiffers) { + searchString = existingFacebookAppIdString; + replaceString = facebookAppIdString; + } + // fbauth is enabled, but appId is not present: add the appId string + else if (isFacebookAuthEnabled && !existingFacebookAppId) { + searchString = ''; + replaceString = `${facebookAppIdString}\n`; + } + // fbauth is disabled && appId string is not present: add dummy appId + else if (!isFacebookAuthEnabled && !existingFacebookAppId) { + searchString = ''; + replaceString = '112233'; + } + + const updatedFileContents = stringsXmlContents.replace(searchString, replaceString); + + fs.writeFileSync(stringsXmlFilePath, updatedFileContents); +}; + +const configureSettingsAndroid = (extensionSettings) => { + const facebookSettings = _.get(extensionSettings, 'providers.facebook'); + + configureFacebookSettingsAndroid(facebookSettings); +}; + +const configureSettingsIos = (extensionSettings) => { + const facebookSettings = _.get(extensionSettings, 'providers.facebook'); + const isFacebookAuthEnabled = _.get(facebookSettings, 'enabled', false); + + if (!isFacebookAuthEnabled) { + return; + } + + configureFacebookSettingsIos(facebookSettings); +}; + +module.exports = { + configureSettingsAndroid, + configureSettingsIos, +}; diff --git a/shoutem.auth/app/build/const.js b/shoutem.auth/app/build/const.js new file mode 100644 index 00000000..ea9cbe11 --- /dev/null +++ b/shoutem.auth/app/build/const.js @@ -0,0 +1,63 @@ +const reactNativeFbSdkDependencies = ` + # React Native FBSDK dependencies + pod 'FBSDKCoreKit' + pod 'FBSDKLoginKit' +`; + +const appDelegateOpenUrl = ` +- (BOOL)application:(UIApplication *)application + openURL:(NSURL *)url + options:(NSDictionary *)options { + + BOOL handled = [[FBSDKApplicationDelegate sharedInstance] application:application + openURL:url + sourceApplication:options[UIApplicationOpenURLOptionsSourceApplicationKey] + annotation:options[UIApplicationOpenURLOptionsAnnotationKey] + ]; + return handled; +} +`; + +const fbSdk = { + ios: { + appDelegate: { + import: '#import ', + didFinishLaunchingWithOptions: '[[FBSDKApplicationDelegate sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions];', + body: appDelegateOpenUrl, + }, + podfile: { + pods: reactNativeFbSdkDependencies, + }, + }, + android: { + mainApplication: { + import: 'import com.facebook.reactnative.androidsdk.FBSDKPackage;', + package: 'packages.add(new FBSDKPackage());', + }, + gradle: { + app: { + dependencies: 'implementation project(\':react-native-fbsdk\')', + }, + settings: 'include \':react-native-fbsdk\'\nproject(\':react-native-fbsdk\').projectDir = new File(rootProject.projectDir, \'../node_modules/react-native-fbsdk/android\')', + }, + manifest: '', + }, +}; + +const appleSignIn = { + ios: { + entitlements: { + addEntitlement: ` + com.apple.developer.applesignin + + Default + + `, + }, + }, +}; + +module.exports = { + fbSdk, + appleSignIn, +}; diff --git a/shoutem.auth/app/build/index.js b/shoutem.auth/app/build/index.js new file mode 100644 index 00000000..9a6b5061 --- /dev/null +++ b/shoutem.auth/app/build/index.js @@ -0,0 +1,28 @@ +const _ = require('lodash'); +const pack = require('../package.json'); +const { + configureSettingsAndroid, + configureSettingsIos, +} = require('./configureFacebookSdk'); +const { injectFbSdk } = require('./injectFbSdk'); +const { injectAppleSignInIos } = require('./injectAppleSignIn'); + +const ext = resourceName => (resourceName ? `${pack.name}.${resourceName}` : pack.name); + +const getExtensionSettings = (appConfiguration) => { + const included = _.get(appConfiguration, 'included'); + const extension = _.find(included, + item => item.type === 'shoutem.core.extensions' && item.id === ext()); + + return _.get(extension, 'attributes.settings'); +}; + +exports.preBuild = function preBuild(appConfiguration) { + const extensionSettings = getExtensionSettings(appConfiguration); + + injectFbSdk(); + injectAppleSignInIos(extensionSettings); + + configureSettingsAndroid(extensionSettings); + configureSettingsIos(extensionSettings); +}; diff --git a/shoutem.auth/app/build/injectAppleSignIn.js b/shoutem.auth/app/build/injectAppleSignIn.js new file mode 100644 index 00000000..7fd5db35 --- /dev/null +++ b/shoutem.auth/app/build/injectAppleSignIn.js @@ -0,0 +1,28 @@ +const _ = require('lodash'); +const { + ANCHORS, + getAppEntitlementsPath, + inject, + projectPath, +} = require('@shoutem/build-tools'); +const { appleSignIn } = require('./const'); + +function injectAppleSignInIos(extensionSettings) { + const appleSettings = _.get(extensionSettings, 'providers.apple'); + const isAppleSignInEnabled = _.get(appleSettings, 'enabled', false); + + if (!isAppleSignInEnabled) { + return; + } + + const appEntitlement = getAppEntitlementsPath({ cwd: projectPath }); + inject( + appEntitlement, + ANCHORS.IOS.ENTITLEMENTS.ADD_ENTITLEMENTS, + appleSignIn.ios.entitlements.addEntitlement, + ); +} + +module.exports = { + injectAppleSignInIos, +}; diff --git a/shoutem.auth/app/build/injectFbSdk.js b/shoutem.auth/app/build/injectFbSdk.js new file mode 100644 index 00000000..fe53c9b7 --- /dev/null +++ b/shoutem.auth/app/build/injectFbSdk.js @@ -0,0 +1,60 @@ +const path = require('path'); +const fs = require('fs-extra'); +const xcode = require('xcode'); +const execSync = require('child_process').execSync; +const { + ANCHORS, + getAndroidManifestPath, + getAppDelegatePath, + getAppGradlePath, + getMainApplicationPath, + getPodfileTemplatePath, + getSettingsGradlePath, + inject, + projectPath, +} = require('@shoutem/build-tools'); +const { fbSdk } = require('./const'); + +function injectFbSdkAndroid() { + const mainApplication = getMainApplicationPath({ cwd: projectPath }); + inject(mainApplication, ANCHORS.ANDROID.MAIN_APPLICATION.IMPORT, fbSdk.android.mainApplication.import); + inject(mainApplication, ANCHORS.ANDROID.MAIN_APPLICATION.GET_PACKAGES, fbSdk.android.mainApplication.package); + + const androidManifestPath = getAndroidManifestPath({ cwd: projectPath }); + inject(androidManifestPath, ANCHORS.ANDROID.MANIFEST.APPLICATION, fbSdk.android.manifest); + + const appGradlePath = getAppGradlePath({ cwd: projectPath }); + inject(appGradlePath, ANCHORS.ANDROID.GRADLE.APP.DEPENDENCIES, fbSdk.android.gradle.app.dependencies); + + const settingsGradlePath = getSettingsGradlePath({ cwd: projectPath }); + inject(settingsGradlePath, ANCHORS.ANDROID.GRADLE.SETTINGS, fbSdk.android.gradle.settings); + +} + +function injectFbSdkIos() { + if (process.platform !== 'darwin') { + console.log('iOS linking for FBSDK is available only on OSX - [Skipping...]'); + return; + } + + const appDelegatePath = getAppDelegatePath({ cwd: projectPath }); + inject(appDelegatePath, ANCHORS.IOS.APP_DELEGATE.IMPORT, fbSdk.ios.appDelegate.import); + inject(appDelegatePath, ANCHORS.IOS.APP_DELEGATE.DID_FINISH_LAUNCHING_WITH_OPTIONS_END, fbSdk.ios.appDelegate.didFinishLaunchingWithOptions); + inject(appDelegatePath, ANCHORS.IOS.APP_DELEGATE.BODY, fbSdk.ios.appDelegate.body); + + const podFileTemplatePath = getPodfileTemplatePath({ cwd: projectPath }); + inject(podFileTemplatePath, ANCHORS.IOS.PODFILE.EXTENSION_DEPENDENCIES, fbSdk.ios.podfile.pods); +} + +/** + * Injects required modifications for react-native-fbsdk as described + * here: https://github.com/facebook/react-native-fbsdk + */ +function injectFbSdk() { + injectFbSdkAndroid(); + injectFbSdkIos(); +} + +module.exports = { + injectFbSdk, +}; diff --git a/shoutem.camera/app/build/const.js b/shoutem.camera/app/build/const.js new file mode 100644 index 00000000..d2bef276 --- /dev/null +++ b/shoutem.camera/app/build/const.js @@ -0,0 +1,13 @@ +const camera = { + android: { + gradle: { + app: { + defaultConfig: 'missingDimensionStrategy \'react-native-camera\', \'general\'', + }, + }, + } +}; + +module.exports = { + camera, +}; diff --git a/shoutem.camera/app/build/index.js b/shoutem.camera/app/build/index.js new file mode 100644 index 00000000..7c322f7d --- /dev/null +++ b/shoutem.camera/app/build/index.js @@ -0,0 +1,7 @@ +'use strict'; + +const injectReactNativeCamera = require('./injectCamera'); + +exports.preBuild = function preBuild(appConfiguration, buildConfiguration) { + injectReactNativeCamera(); +}; diff --git a/shoutem.camera/app/build/injectCamera.js b/shoutem.camera/app/build/injectCamera.js new file mode 100644 index 00000000..6a9efd41 --- /dev/null +++ b/shoutem.camera/app/build/injectCamera.js @@ -0,0 +1,25 @@ +const { + getAppGradlePath, + inject, + ANCHORS, + projectPath, +} = require('@shoutem/build-tools'); +const { camera } = require('./const'); + +// cwd for build steps is extension/app directory. we want to run this on project directory + +function injectAndroid() { + // app/build.gradle mods + const appGradlePath = getAppGradlePath({ cwd: projectPath }); + inject(appGradlePath, ANCHORS.ANDROID.GRADLE.APP.DEFAULT_CONFIG, camera.android.gradle.app.defaultConfig); +} + +/** + * Injects required modifications for react-native-camera package as described + * here: https://react-native-community.github.io/react-native-camera/docs/installation.html#modifying-buildgradle + */ +function injectCamera() { + injectAndroid(); +} + +module.exports = injectCamera; diff --git a/shoutem.firebase/app/build/const.js b/shoutem.firebase/app/build/const.js new file mode 100644 index 00000000..3f2ae96c --- /dev/null +++ b/shoutem.firebase/app/build/const.js @@ -0,0 +1,8 @@ +const pack = require('../package.json'); + +// defines scope for the current extension state within the global app's state +function ext(resourceName) { + return resourceName ? `${pack.name}.${resourceName}` : pack.name; +} + +module.exports = { ext }; diff --git a/shoutem.firebase/app/build/injectFirebaseSettingsFile.js b/shoutem.firebase/app/build/injectFirebaseSettingsFile.js new file mode 100644 index 00000000..27da17a0 --- /dev/null +++ b/shoutem.firebase/app/build/injectFirebaseSettingsFile.js @@ -0,0 +1,26 @@ +const _ = require('lodash'); +const fs = require('fs-extra'); +const path = require('path'); +const { projectPath } = require('@shoutem/build-tools'); +const { ext } = require('./const'); + +function injectFirebaseSettingsFile() { + const filePath = path.resolve(projectPath, 'firebase.json'); + + try { + fs.ensureFileSync(filePath); + const existingConfig = fs.readJsonSync(filePath, { throws: false }); + const config = { messaging_ios_auto_register_for_remote_messages: false }; + const actionTaken = _.isEmpty(existingConfig) ? 'Created' : 'Modified'; + const newConfig = existingConfig ? _.merge(existingConfig, config) : config; + fs.writeJsonSync(filePath, newConfig); + + console.log(`[${ext()}] - ${actionTaken} root/firebase.json`); + } catch (error) { + console.log(error); + } +} + +module.exports = { + injectFirebaseSettingsFile, +}; diff --git a/shoutem.firebase/app/build/injectReactNativePush.js b/shoutem.firebase/app/build/injectReactNativePush.js new file mode 100644 index 00000000..e5ac64f7 --- /dev/null +++ b/shoutem.firebase/app/build/injectReactNativePush.js @@ -0,0 +1,133 @@ +/* eslint-disable max-len */ +const { + getAppGradlePath, + getAndroidManifestPath, + getAppDelegateHeaderPath, + getAppDelegatePath, + getRootGradlePath, + inject, + replace, + ANCHORS, + projectPath, +} = require('@shoutem/build-tools'); + +const androidPermissions = ` + + + +`; + +const androidManifestApplication = ` + + + + + + + + + + + + + + + +`; + +const androidRootGradle = "classpath('com.google.gms:google-services:4.3.3')"; +const androidPlugins = "apply plugin: 'com.google.gms.google-services'"; + +const appDelegateHeaderImport = '#import '; +const appDelegateHeaderSearch = '@interface AppDelegate : UIResponder '; +const appDelegateHeaderReplace = '@interface AppDelegate : UIResponder '; + +const appDelegateImport = ` +#import +#import +`; + +const appDelegateBody = ` +// Required to register for notifications +- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings +{ + [RNCPushNotificationIOS didRegisterUserNotificationSettings:notificationSettings]; +} +// Required for the register event. +- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken +{ + [RNCPushNotificationIOS didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; +} +// Required for the notification event. You must call the completion handler after handling the remote notification. +- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo +fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler +{ + [RNCPushNotificationIOS didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler]; +} +// Required for the registrationError event. +- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error +{ + [RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error]; +} +// IOS 10+ Required for localNotification event +- (void)userNotificationCenter:(UNUserNotificationCenter *)center +didReceiveNotificationResponse:(UNNotificationResponse *)response + withCompletionHandler:(void (^)(void))completionHandler +{ + [RNCPushNotificationIOS didReceiveNotificationResponse:response]; + completionHandler(); +} +// IOS 4-10 Required for the localNotification event. +- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification +{ + [RNCPushNotificationIOS didReceiveLocalNotification:notification]; +} +//Called when a notification is delivered to a foreground app. +-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler +{ + completionHandler(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge); +} +`; + +const appDelegateDidFinishLaunching = ` +UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + center.delegate = self; +`; + +function injectIos() { + // appDelegate.m mods + const appDelegatePath = getAppDelegatePath({ cwd: projectPath }); + inject(appDelegatePath, ANCHORS.IOS.APP_DELEGATE.IMPORT, appDelegateImport); + inject(appDelegatePath, ANCHORS.IOS.APP_DELEGATE.BODY, appDelegateBody); + inject(appDelegatePath, ANCHORS.IOS.APP_DELEGATE.DID_FINISH_LAUNCHING_WITH_OPTIONS, appDelegateDidFinishLaunching); + + // appDelegate.h mods + const appDelegateHeaderPath = getAppDelegateHeaderPath({ cwd: projectPath }); + inject(appDelegateHeaderPath, ANCHORS.IOS.APP_DELEGATE_HEADER.IMPORT, appDelegateHeaderImport); + replace(appDelegateHeaderPath, appDelegateHeaderSearch, appDelegateHeaderReplace); +} + +function injectAndroid() { + // app/build.gradle mods + const appGradlePath = getAppGradlePath({ cwd: projectPath }); + inject(appGradlePath, ANCHORS.ANDROID.GRADLE.APP.PLUGINS, androidPlugins); + + const rootGradlePath = getRootGradlePath({ cwd: projectPath }); + inject(rootGradlePath, ANCHORS.ANDROID.GRADLE.ROOT_GRADLE, androidRootGradle); + + const manifestPath = getAndroidManifestPath({ cwd: projectPath }); + inject(manifestPath, ANCHORS.ANDROID.MANIFEST.ROOT, androidPermissions); + inject(manifestPath, ANCHORS.ANDROID.MANIFEST.APPLICATION, androidManifestApplication); +} + +function injectReactNativePush() { + injectIos(); + injectAndroid(); +} + +module.exports = { + injectReactNativePush, +}; diff --git a/shoutem.firebase/app/build/templates/GoogleService-Info.plist b/shoutem.firebase/app/build/templates/GoogleService-Info.plist new file mode 100644 index 00000000..5bb3933f --- /dev/null +++ b/shoutem.firebase/app/build/templates/GoogleService-Info.plist @@ -0,0 +1,36 @@ + + + + + CLIENT_ID + 1-2.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.2-1 + API_KEY + AIzaSyCsSdR6dbDnpEh9bE2Sn6pU3c-Qgn_qplY + GCM_SENDER_ID + 123456 + PLIST_VERSION + 1 + BUNDLE_ID + hr.apps.dummy + PROJECT_ID + dummyproj + STORAGE_BUCKET + dummyproj.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:2:ios:3 + DATABASE_URL + https://dummyproj.firebaseio.com + + diff --git a/shoutem.firebase/app/build/templates/google-services.json b/shoutem.firebase/app/build/templates/google-services.json new file mode 100644 index 00000000..8368c0ee --- /dev/null +++ b/shoutem.firebase/app/build/templates/google-services.json @@ -0,0 +1,47 @@ +{ + "project_info": { + "project_number": "123", + "firebase_url": "https://dummyproj.firebaseio.com", + "project_id": "dummyproj", + "storage_bucket": "dummyproj.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:2:android:3", + "android_client_info": { + "package_name": "hr.apps.dummyproj" + } + }, + "oauth_client": [ + { + "client_id": "1-2.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "1-2" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "1-2.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "1-2.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "hr.apps.dummyproj" + } + } + ] + } + } + } + ], + "configuration_version": "1" +} diff --git a/shoutem.in-app-purchases/app/build/index.js b/shoutem.in-app-purchases/app/build/index.js new file mode 100644 index 00000000..8e59a088 --- /dev/null +++ b/shoutem.in-app-purchases/app/build/index.js @@ -0,0 +1,7 @@ +const { injectStoreKit } = require('./injectStoreKit'); +const { injectBillingPermission } = require('./injectBillingPermission'); + +exports.preBuild = function preBuild() { + injectStoreKit(); + injectBillingPermission(); +}; diff --git a/shoutem.in-app-purchases/app/build/injectBillingPermission.js b/shoutem.in-app-purchases/app/build/injectBillingPermission.js new file mode 100644 index 00000000..5dd90624 --- /dev/null +++ b/shoutem.in-app-purchases/app/build/injectBillingPermission.js @@ -0,0 +1,20 @@ +const { + getAndroidManifestPath, + inject, + ANCHORS, + projectPath, +} = require('@shoutem/build-tools'); + +const billingPermission = ``; + +function injectBillingPermission() { + const manifestPath = getAndroidManifestPath({ cwd: projectPath }); + + inject(manifestPath, ANCHORS.ANDROID.MANIFEST.ROOT, billingPermission); + + console.log(`Android: Added BILLING permission to Android manifest`); +} + +module.exports = { + injectBillingPermission, +}; diff --git a/shoutem.in-app-purchases/app/build/injectStoreKit.js b/shoutem.in-app-purchases/app/build/injectStoreKit.js new file mode 100644 index 00000000..29a3dfd2 --- /dev/null +++ b/shoutem.in-app-purchases/app/build/injectStoreKit.js @@ -0,0 +1,16 @@ +const fs = require('fs-extra'); +const xcode = require('xcode'); +const { getXcodeProjectPath, prependProjectPath } = require('@shoutem/build-tools'); + +function injectStoreKit() { + const iOSdirPath = prependProjectPath('ios'); + const xcodeProjectPath = getXcodeProjectPath({ cwd: iOSdirPath }); + const xcodeProject = xcode.project(xcodeProjectPath).parseSync(); + + xcodeProject.addFramework('StoreKit.framework'); + fs.writeFileSync(xcodeProjectPath, xcodeProject.writeSync()); + + console.log(`iOS: Added StoreKit to ${xcodeProjectPath} frameworks`); +} + +module.exports = { injectStoreKit }; diff --git a/shoutem.push-notifications/app/build/const.js b/shoutem.push-notifications/app/build/const.js new file mode 100644 index 00000000..c6b6c4cb --- /dev/null +++ b/shoutem.push-notifications/app/build/const.js @@ -0,0 +1,6 @@ +const PUSH_NOTIFICATION_PERMISION_HANDLER = + "pod 'Permission-Notifications', :path => '../node_modules/react-native-permissions/ios/Notifications.podspec'"; + +module.exports = { + PUSH_NOTIFICATION_PERMISION_HANDLER, +}; diff --git a/shoutem.push-notifications/app/build/index.js b/shoutem.push-notifications/app/build/index.js new file mode 100644 index 00000000..47688a19 --- /dev/null +++ b/shoutem.push-notifications/app/build/index.js @@ -0,0 +1,5 @@ +const { injectPermissionHandlerIos } = require('./injectPermissions'); + +exports.preBuild = function preBuild() { + injectPermissionHandlerIos(); +}; diff --git a/shoutem.push-notifications/app/build/injectPermissions.js b/shoutem.push-notifications/app/build/injectPermissions.js new file mode 100644 index 00000000..8a2b1a26 --- /dev/null +++ b/shoutem.push-notifications/app/build/injectPermissions.js @@ -0,0 +1,28 @@ +const { + ANCHORS, + getPodfileTemplatePath, + inject, + projectPath, +} = require('@shoutem/build-tools'); +const { + PUSH_NOTIFICATION_PERMISION_HANDLER, +} = require('./const'); + +/** + * ios/Podfile.template injects required modifications for react-native-permissions package as described + * here: https://github.com/react-native-community/react-native-permissions#ios + * + * This must be run before pod install step + */ + +function injectPermissionHandlerIos() { + const podfileTemplatePath = getPodfileTemplatePath({ cwd: projectPath }); + + inject( + podfileTemplatePath, + ANCHORS.IOS.PODFILE.EXTENSION_DEPENDENCIES, + PUSH_NOTIFICATION_PERMISION_HANDLER, + ); +} + +module.exports = { injectPermissionHandlerIos }; diff --git a/shoutem.radio/app/build/runtime-path-fix.js b/shoutem.radio/app/build/runtime-path-fix.js new file mode 100755 index 00000000..78e3810b --- /dev/null +++ b/shoutem.radio/app/build/runtime-path-fix.js @@ -0,0 +1,56 @@ +const fs = require('fs-extra'); +const xcode = require('xcode'); +const _ = require('lodash'); +const { getXcodeProjectPath } = require('@shoutem/build-tools'); +const pbxprojPath = getXcodeProjectPath(); +const xcodeProject = xcode.project(pbxprojPath).parseSync(); +const propKey = 'LD_RUNPATH_SEARCH_PATHS'; +const swiftRunpath = '/usr/lib/swift'; +const requiredRunpaths = '/usr/lib/swift $(inherited) @executable_path/Frameworks'; +function unquote(str = '') { + return str.replace(/^"(.*)"$/, '$1'); +} +function quote(str = '') { + return `"${str}"`; +} +function getRunpaths(buildType) { + if (buildType) { + return xcodeProject.getBuildProperty(propKey, buildType); + } + return xcodeProject.getBuildProperty(propKey); +} +function determineBuildType() { + if (!getRunpaths()) { + const debugBuild = getRunpaths('Debug'); + const releaseBuild = getRunpaths('Release'); + if (debugBuild && releaseBuild) { + return 'both'; + } + } + return 'default'; +} +function prependSwiftRunpath(buildType) { + const currentPaths = getRunpaths(buildType); + if (!currentPaths) { + xcodeProject.addBuildProperty(propKey, requiredRunpaths, buildType); + return; + } + if (_.includes(currentPaths, swiftRunpath)) { + return; + } + const newRunpaths = quote(`${swiftRunpath} ${unquote(currentPaths)}`); + xcodeProject.updateBuildProperty(propKey, newRunpaths, buildType); +} +function prependAllSwiftRunpath() { + const buildType = determineBuildType(); + if (buildType === 'default') { + prependSwiftRunpath(undefined); + } else { + prependSwiftRunpath('Debug'); + prependSwiftRunpath('Release'); + } + fs.writeFileSync(pbxprojPath, xcodeProject.writeSync()); +} +module.exports = { + prependAllSwiftRunpath, +}; diff --git a/shoutem.shopify/app/build/const.js b/shoutem.shopify/app/build/const.js new file mode 100644 index 00000000..75250c9b --- /dev/null +++ b/shoutem.shopify/app/build/const.js @@ -0,0 +1,23 @@ +const shopify = { + ios: { + }, + android: { + manifest: { + root: { + usesSdk: '' + } + }, + gradle: { + app: { + compileOptions: `compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + }` + } + } + }, +}; + +module.exports = { + shopify, +}; diff --git a/shoutem.shopify/app/build/index.js b/shoutem.shopify/app/build/index.js new file mode 100644 index 00000000..f8cb3adf --- /dev/null +++ b/shoutem.shopify/app/build/index.js @@ -0,0 +1,6 @@ +const { injectShopifyAndroid, injectShopifyIos } = require('./injectShopify'); + +exports.preBuild = function preBuild() { + injectShopifyAndroid(); + injectShopifyIos(); +} diff --git a/shoutem.shopify/app/build/injectShopify.js b/shoutem.shopify/app/build/injectShopify.js new file mode 100644 index 00000000..5d53d100 --- /dev/null +++ b/shoutem.shopify/app/build/injectShopify.js @@ -0,0 +1,23 @@ +const { + getAndroidManifestPath, + getAppGradlePath, + projectPath, + inject, + ANCHORS, +} = require('@shoutem/build-tools'); +const { shopify } = require('./const'); + +function injectShopifyAndroid() { + const appManifest = getAndroidManifestPath({ cwd: projectPath }); + inject(appManifest, ANCHORS.ANDROID.MANIFEST.ROOT, shopify.android.manifest.root.usesSdk); + + const appGradlePath = getAppGradlePath({ cwd: projectPath }); + inject(appGradlePath, ANCHORS.ANDROID.GRADLE.APP.ANDROID_END, shopify.android.gradle.app.compileOptions); +} + +function injectShopifyIos() {} + +module.exports = { + injectShopifyAndroid, + injectShopifyIos, +};