Repositorio del curso CCOM4030 el semestre B91 del proyecto Paz para la Mujer

prepare.js 49KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172
  1. /**
  2. Licensed to the Apache Software Foundation (ASF) under one
  3. or more contributor license agreements. See the NOTICE file
  4. distributed with this work for additional information
  5. regarding copyright ownership. The ASF licenses this file
  6. to you under the Apache License, Version 2.0 (the
  7. "License"); you may not use this file except in compliance
  8. with the License. You may obtain a copy of the License at
  9. http://www.apache.org/licenses/LICENSE-2.0
  10. Unless required by applicable law or agreed to in writing,
  11. software distributed under the License is distributed on an
  12. "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  13. KIND, either express or implied. See the License for the
  14. specific language governing permissions and limitations
  15. under the License.
  16. */
  17. 'use strict';
  18. var Q = require('q');
  19. var fs = require('fs');
  20. var path = require('path');
  21. var shell = require('shelljs');
  22. var unorm = require('unorm');
  23. var plist = require('plist');
  24. var URL = require('url');
  25. var events = require('cordova-common').events;
  26. var xmlHelpers = require('cordova-common').xmlHelpers;
  27. var ConfigParser = require('cordova-common').ConfigParser;
  28. var CordovaError = require('cordova-common').CordovaError;
  29. var PlatformJson = require('cordova-common').PlatformJson;
  30. var PlatformMunger = require('cordova-common').ConfigChanges.PlatformMunger;
  31. var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
  32. var FileUpdater = require('cordova-common').FileUpdater;
  33. var projectFile = require('./projectFile');
  34. // launch storyboard and related constants
  35. var LAUNCHIMAGE_BUILD_SETTING = 'ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME';
  36. var LAUNCHIMAGE_BUILD_SETTING_VALUE = 'LaunchImage';
  37. var UI_LAUNCH_STORYBOARD_NAME = 'UILaunchStoryboardName';
  38. var CDV_LAUNCH_STORYBOARD_NAME = 'CDVLaunchScreen';
  39. var IMAGESET_COMPACT_SIZE_CLASS = 'compact';
  40. var CDV_ANY_SIZE_CLASS = 'any';
  41. module.exports.prepare = function (cordovaProject, options) {
  42. var self = this;
  43. var platformJson = PlatformJson.load(this.locations.root, 'ios');
  44. var munger = new PlatformMunger('ios', this.locations.root, platformJson, new PluginInfoProvider());
  45. this._config = updateConfigFile(cordovaProject.projectConfig, munger, this.locations);
  46. // Update own www dir with project's www assets and plugins' assets and js-files
  47. return Q.when(updateWww(cordovaProject, this.locations))
  48. .then(function () {
  49. // update project according to config.xml changes.
  50. return updateProject(self._config, self.locations);
  51. })
  52. .then(function () {
  53. updateIcons(cordovaProject, self.locations);
  54. updateSplashScreens(cordovaProject, self.locations);
  55. updateLaunchStoryboardImages(cordovaProject, self.locations);
  56. updateFileResources(cordovaProject, self.locations);
  57. })
  58. .then(function () {
  59. events.emit('verbose', 'Prepared iOS project successfully');
  60. });
  61. };
  62. module.exports.clean = function (options) {
  63. // A cordovaProject isn't passed into the clean() function, because it might have
  64. // been called from the platform shell script rather than the CLI. Check for the
  65. // noPrepare option passed in by the non-CLI clean script. If that's present, or if
  66. // there's no config.xml found at the project root, then don't clean prepared files.
  67. var projectRoot = path.resolve(this.root, '../..');
  68. var projectConfigFile = path.join(projectRoot, 'config.xml');
  69. if ((options && options.noPrepare) || !fs.existsSync(projectConfigFile) ||
  70. !fs.existsSync(this.locations.configXml)) {
  71. return Q();
  72. }
  73. var projectConfig = new ConfigParser(this.locations.configXml);
  74. var self = this;
  75. return Q().then(function () {
  76. cleanWww(projectRoot, self.locations);
  77. cleanIcons(projectRoot, projectConfig, self.locations);
  78. cleanSplashScreens(projectRoot, projectConfig, self.locations);
  79. cleanLaunchStoryboardImages(projectRoot, projectConfig, self.locations);
  80. cleanFileResources(projectRoot, projectConfig, self.locations);
  81. });
  82. };
  83. /**
  84. * Updates config files in project based on app's config.xml and config munge,
  85. * generated by plugins.
  86. *
  87. * @param {ConfigParser} sourceConfig A project's configuration that will
  88. * be merged into platform's config.xml
  89. * @param {ConfigChanges} configMunger An initialized ConfigChanges instance
  90. * for this platform.
  91. * @param {Object} locations A map of locations for this platform
  92. *
  93. * @return {ConfigParser} An instance of ConfigParser, that
  94. * represents current project's configuration. When returned, the
  95. * configuration is already dumped to appropriate config.xml file.
  96. */
  97. function updateConfigFile (sourceConfig, configMunger, locations) {
  98. events.emit('verbose', 'Generating platform-specific config.xml from defaults for iOS at ' + locations.configXml);
  99. // First cleanup current config and merge project's one into own
  100. // Overwrite platform config.xml with defaults.xml.
  101. shell.cp('-f', locations.defaultConfigXml, locations.configXml);
  102. // Then apply config changes from global munge to all config files
  103. // in project (including project's config)
  104. configMunger.reapply_global_munge().save_all();
  105. events.emit('verbose', 'Merging project\'s config.xml into platform-specific iOS config.xml');
  106. // Merge changes from app's config.xml into platform's one
  107. var config = new ConfigParser(locations.configXml);
  108. xmlHelpers.mergeXml(sourceConfig.doc.getroot(),
  109. config.doc.getroot(), 'ios', /* clobber= */true);
  110. config.write();
  111. return config;
  112. }
  113. /**
  114. * Logs all file operations via the verbose event stream, indented.
  115. */
  116. function logFileOp (message) {
  117. events.emit('verbose', ' ' + message);
  118. }
  119. /**
  120. * Updates platform 'www' directory by replacing it with contents of
  121. * 'platform_www' and app www. Also copies project's overrides' folder into
  122. * the platform 'www' folder
  123. *
  124. * @param {Object} cordovaProject An object which describes cordova project.
  125. * @param {boolean} destinations An object that contains destinations
  126. * paths for www files.
  127. */
  128. function updateWww (cordovaProject, destinations) {
  129. var sourceDirs = [
  130. path.relative(cordovaProject.root, cordovaProject.locations.www),
  131. path.relative(cordovaProject.root, destinations.platformWww)
  132. ];
  133. // If project contains 'merges' for our platform, use them as another overrides
  134. var merges_path = path.join(cordovaProject.root, 'merges', 'ios');
  135. if (fs.existsSync(merges_path)) {
  136. events.emit('verbose', 'Found "merges/ios" folder. Copying its contents into the iOS project.');
  137. sourceDirs.push(path.join('merges', 'ios'));
  138. }
  139. var targetDir = path.relative(cordovaProject.root, destinations.www);
  140. events.emit(
  141. 'verbose', 'Merging and updating files from [' + sourceDirs.join(', ') + '] to ' + targetDir);
  142. FileUpdater.mergeAndUpdateDir(
  143. sourceDirs, targetDir, { rootDir: cordovaProject.root }, logFileOp);
  144. }
  145. /**
  146. * Cleans all files from the platform 'www' directory.
  147. */
  148. function cleanWww (projectRoot, locations) {
  149. var targetDir = path.relative(projectRoot, locations.www);
  150. events.emit('verbose', 'Cleaning ' + targetDir);
  151. // No source paths are specified, so mergeAndUpdateDir() will clear the target directory.
  152. FileUpdater.mergeAndUpdateDir(
  153. [], targetDir, { rootDir: projectRoot, all: true }, logFileOp);
  154. }
  155. /**
  156. * Updates project structure and AndroidManifest according to project's configuration.
  157. *
  158. * @param {ConfigParser} platformConfig A project's configuration that will
  159. * be used to update project
  160. * @param {Object} locations A map of locations for this platform (In/Out)
  161. */
  162. function updateProject (platformConfig, locations) {
  163. // CB-6992 it is necessary to normalize characters
  164. // because node and shell scripts handles unicode symbols differently
  165. // We need to normalize the name to NFD form since iOS uses NFD unicode form
  166. var name = unorm.nfd(platformConfig.name());
  167. var version = platformConfig.version();
  168. var displayName = platformConfig.shortName && platformConfig.shortName();
  169. var originalName = path.basename(locations.xcodeCordovaProj);
  170. // Update package id (bundle id)
  171. var plistFile = path.join(locations.xcodeCordovaProj, originalName + '-Info.plist');
  172. var infoPlist = plist.parse(fs.readFileSync(plistFile, 'utf8'));
  173. // Update version (bundle version)
  174. infoPlist['CFBundleShortVersionString'] = version;
  175. var CFBundleVersion = platformConfig.getAttribute('ios-CFBundleVersion') || default_CFBundleVersion(version);
  176. infoPlist['CFBundleVersion'] = CFBundleVersion;
  177. if (platformConfig.getAttribute('defaultlocale')) {
  178. infoPlist['CFBundleDevelopmentRegion'] = platformConfig.getAttribute('defaultlocale');
  179. }
  180. if (displayName) {
  181. infoPlist['CFBundleDisplayName'] = displayName;
  182. }
  183. // replace Info.plist ATS entries according to <access> and <allow-navigation> config.xml entries
  184. var ats = writeATSEntries(platformConfig);
  185. if (Object.keys(ats).length > 0) {
  186. infoPlist['NSAppTransportSecurity'] = ats;
  187. } else {
  188. delete infoPlist['NSAppTransportSecurity'];
  189. }
  190. handleOrientationSettings(platformConfig, infoPlist);
  191. updateProjectPlistForLaunchStoryboard(platformConfig, infoPlist);
  192. /* eslint-disable no-tabs */
  193. // Write out the plist file with the same formatting as Xcode does
  194. var info_contents = plist.build(infoPlist, { indent: ' ', offset: -1 });
  195. /* eslint-enable no-tabs */
  196. info_contents = info_contents.replace(/<string>[\s\r\n]*<\/string>/g, '<string></string>');
  197. fs.writeFileSync(plistFile, info_contents, 'utf-8');
  198. events.emit('verbose', 'Wrote out iOS Bundle Version "' + version + '" to ' + plistFile);
  199. return handleBuildSettings(platformConfig, locations, infoPlist).then(function () {
  200. if (name === originalName) {
  201. events.emit('verbose', 'iOS Product Name has not changed (still "' + originalName + '")');
  202. return Q();
  203. } else { // CB-11712 <name> was changed, we don't support it'
  204. var errorString =
  205. 'The product name change (<name> tag) in config.xml is not supported dynamically.\n' +
  206. 'To change your product name, you have to remove, then add your ios platform again.\n' +
  207. 'Make sure you save your plugins beforehand using `cordova plugin save`.\n' +
  208. '\tcordova plugin save\n' +
  209. '\tcordova platform rm ios\n' +
  210. '\tcordova platform add ios\n'
  211. ;
  212. return Q.reject(new CordovaError(errorString));
  213. }
  214. });
  215. }
  216. function handleOrientationSettings (platformConfig, infoPlist) {
  217. switch (getOrientationValue(platformConfig)) {
  218. case 'portrait':
  219. infoPlist['UIInterfaceOrientation'] = [ 'UIInterfaceOrientationPortrait' ];
  220. infoPlist['UISupportedInterfaceOrientations'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationPortraitUpsideDown' ];
  221. infoPlist['UISupportedInterfaceOrientations~ipad'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationPortraitUpsideDown' ];
  222. break;
  223. case 'landscape':
  224. infoPlist['UIInterfaceOrientation'] = [ 'UIInterfaceOrientationLandscapeLeft' ];
  225. infoPlist['UISupportedInterfaceOrientations'] = [ 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
  226. infoPlist['UISupportedInterfaceOrientations~ipad'] = [ 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
  227. break;
  228. case 'all':
  229. infoPlist['UIInterfaceOrientation'] = [ 'UIInterfaceOrientationPortrait' ];
  230. infoPlist['UISupportedInterfaceOrientations'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationPortraitUpsideDown', 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
  231. infoPlist['UISupportedInterfaceOrientations~ipad'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationPortraitUpsideDown', 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
  232. break;
  233. case 'default':
  234. infoPlist['UISupportedInterfaceOrientations'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
  235. infoPlist['UISupportedInterfaceOrientations~ipad'] = [ 'UIInterfaceOrientationPortrait', 'UIInterfaceOrientationPortraitUpsideDown', 'UIInterfaceOrientationLandscapeLeft', 'UIInterfaceOrientationLandscapeRight' ];
  236. delete infoPlist['UIInterfaceOrientation'];
  237. }
  238. }
  239. function handleBuildSettings (platformConfig, locations, infoPlist) {
  240. var pkg = platformConfig.getAttribute('ios-CFBundleIdentifier') || platformConfig.packageName();
  241. var targetDevice = parseTargetDevicePreference(platformConfig.getPreference('target-device', 'ios'));
  242. var deploymentTarget = platformConfig.getPreference('deployment-target', 'ios');
  243. var needUpdatedBuildSettingsForLaunchStoryboard = checkIfBuildSettingsNeedUpdatedForLaunchStoryboard(platformConfig, infoPlist);
  244. var swiftVersion = platformConfig.getPreference('SwiftVersion', 'ios');
  245. var project;
  246. try {
  247. project = projectFile.parse(locations);
  248. } catch (err) {
  249. return Q.reject(new CordovaError('Could not parse ' + locations.pbxproj + ': ' + err));
  250. }
  251. var origPkg = project.xcode.getBuildProperty('PRODUCT_BUNDLE_IDENTIFIER');
  252. // no build settings provided and we don't need to update build settings for launch storyboards,
  253. // then we don't need to parse and update .pbxproj file
  254. if (origPkg === pkg && !targetDevice && !deploymentTarget && !needUpdatedBuildSettingsForLaunchStoryboard && !swiftVersion) {
  255. return Q();
  256. }
  257. if (origPkg !== pkg) {
  258. events.emit('verbose', 'Set PRODUCT_BUNDLE_IDENTIFIER to ' + pkg + '.');
  259. project.xcode.updateBuildProperty('PRODUCT_BUNDLE_IDENTIFIER', pkg);
  260. }
  261. if (targetDevice) {
  262. events.emit('verbose', 'Set TARGETED_DEVICE_FAMILY to ' + targetDevice + '.');
  263. project.xcode.updateBuildProperty('TARGETED_DEVICE_FAMILY', targetDevice);
  264. }
  265. if (deploymentTarget) {
  266. events.emit('verbose', 'Set IPHONEOS_DEPLOYMENT_TARGET to "' + deploymentTarget + '".');
  267. project.xcode.updateBuildProperty('IPHONEOS_DEPLOYMENT_TARGET', deploymentTarget);
  268. }
  269. if (swiftVersion) {
  270. events.emit('verbose', 'Set SwiftVersion to "' + swiftVersion + '".');
  271. project.xcode.updateBuildProperty('SWIFT_VERSION', swiftVersion);
  272. }
  273. updateBuildSettingsForLaunchStoryboard(project.xcode, platformConfig, infoPlist);
  274. project.write();
  275. return Q();
  276. }
  277. function mapIconResources (icons, iconsDir) {
  278. // See https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/MobileHIG/IconMatrix.html
  279. // for launch images sizes reference.
  280. var platformIcons = [
  281. { dest: 'icon-20.png', width: 20, height: 20 },
  282. { dest: 'icon-20@2x.png', width: 40, height: 40 },
  283. { dest: 'icon-20@3x.png', width: 60, height: 60 },
  284. { dest: 'icon-40.png', width: 40, height: 40 },
  285. { dest: 'icon-40@2x.png', width: 80, height: 80 },
  286. { dest: 'icon-50.png', width: 50, height: 50 },
  287. { dest: 'icon-50@2x.png', width: 100, height: 100 },
  288. { dest: 'icon-60@2x.png', width: 120, height: 120 },
  289. { dest: 'icon-60@3x.png', width: 180, height: 180 },
  290. { dest: 'icon-72.png', width: 72, height: 72 },
  291. { dest: 'icon-72@2x.png', width: 144, height: 144 },
  292. { dest: 'icon-76.png', width: 76, height: 76 },
  293. { dest: 'icon-76@2x.png', width: 152, height: 152 },
  294. { dest: 'icon-83.5@2x.png', width: 167, height: 167 },
  295. { dest: 'icon-1024.png', width: 1024, height: 1024 },
  296. { dest: 'icon-29.png', width: 29, height: 29 },
  297. { dest: 'icon-29@2x.png', width: 58, height: 58 },
  298. { dest: 'icon-29@3x.png', width: 87, height: 87 },
  299. { dest: 'icon.png', width: 57, height: 57 },
  300. { dest: 'icon@2x.png', width: 114, height: 114 },
  301. { dest: 'icon-24@2x.png', width: 48, height: 48 },
  302. { dest: 'icon-27.5@2x.png', width: 55, height: 55 },
  303. { dest: 'icon-44@2x.png', width: 88, height: 88 },
  304. { dest: 'icon-86@2x.png', width: 172, height: 172 },
  305. { dest: 'icon-98@2x.png', width: 196, height: 196 }
  306. ];
  307. var pathMap = {};
  308. platformIcons.forEach(function (item) {
  309. var icon = icons.getBySize(item.width, item.height) || icons.getDefault();
  310. if (icon) {
  311. var target = path.join(iconsDir, item.dest);
  312. pathMap[target] = icon.src;
  313. }
  314. });
  315. return pathMap;
  316. }
  317. function getIconsDir (projectRoot, platformProjDir) {
  318. var iconsDir;
  319. var xcassetsExists = folderExists(path.join(projectRoot, platformProjDir, 'Images.xcassets/'));
  320. if (xcassetsExists) {
  321. iconsDir = path.join(platformProjDir, 'Images.xcassets/AppIcon.appiconset/');
  322. } else {
  323. iconsDir = path.join(platformProjDir, 'Resources/icons/');
  324. }
  325. return iconsDir;
  326. }
  327. function updateIcons (cordovaProject, locations) {
  328. var icons = cordovaProject.projectConfig.getIcons('ios');
  329. if (icons.length === 0) {
  330. events.emit('verbose', 'This app does not have icons defined');
  331. return;
  332. }
  333. var platformProjDir = path.relative(cordovaProject.root, locations.xcodeCordovaProj);
  334. var iconsDir = getIconsDir(cordovaProject.root, platformProjDir);
  335. var resourceMap = mapIconResources(icons, iconsDir);
  336. events.emit('verbose', 'Updating icons at ' + iconsDir);
  337. FileUpdater.updatePaths(
  338. resourceMap, { rootDir: cordovaProject.root }, logFileOp);
  339. }
  340. function cleanIcons (projectRoot, projectConfig, locations) {
  341. var icons = projectConfig.getIcons('ios');
  342. if (icons.length > 0) {
  343. var platformProjDir = path.relative(projectRoot, locations.xcodeCordovaProj);
  344. var iconsDir = getIconsDir(projectRoot, platformProjDir);
  345. var resourceMap = mapIconResources(icons, iconsDir);
  346. Object.keys(resourceMap).forEach(function (targetIconPath) {
  347. resourceMap[targetIconPath] = null;
  348. });
  349. events.emit('verbose', 'Cleaning icons at ' + iconsDir);
  350. // Source paths are removed from the map, so updatePaths() will delete the target files.
  351. FileUpdater.updatePaths(
  352. resourceMap, { rootDir: projectRoot, all: true }, logFileOp);
  353. }
  354. }
  355. function mapSplashScreenResources (splashScreens, splashScreensDir) {
  356. var platformSplashScreens = [
  357. { dest: 'Default~iphone.png', width: 320, height: 480 },
  358. { dest: 'Default@2x~iphone.png', width: 640, height: 960 },
  359. { dest: 'Default-Portrait~ipad.png', width: 768, height: 1024 },
  360. { dest: 'Default-Portrait@2x~ipad.png', width: 1536, height: 2048 },
  361. { dest: 'Default-Landscape~ipad.png', width: 1024, height: 768 },
  362. { dest: 'Default-Landscape@2x~ipad.png', width: 2048, height: 1536 },
  363. { dest: 'Default-568h@2x~iphone.png', width: 640, height: 1136 },
  364. { dest: 'Default-667h.png', width: 750, height: 1334 },
  365. { dest: 'Default-736h.png', width: 1242, height: 2208 },
  366. { dest: 'Default-Landscape-736h.png', width: 2208, height: 1242 },
  367. { dest: 'Default-2436h.png', width: 1125, height: 2436 },
  368. { dest: 'Default-Landscape-2436h.png', width: 2436, height: 1125 }
  369. ];
  370. var pathMap = {};
  371. platformSplashScreens.forEach(function (item) {
  372. var splash = splashScreens.getBySize(item.width, item.height);
  373. if (splash) {
  374. var target = path.join(splashScreensDir, item.dest);
  375. pathMap[target] = splash.src;
  376. }
  377. });
  378. return pathMap;
  379. }
  380. function getSplashScreensDir (projectRoot, platformProjDir) {
  381. var splashScreensDir;
  382. var xcassetsExists = folderExists(path.join(projectRoot, platformProjDir, 'Images.xcassets/'));
  383. if (xcassetsExists) {
  384. splashScreensDir = path.join(platformProjDir, 'Images.xcassets/LaunchImage.launchimage/');
  385. } else {
  386. splashScreensDir = path.join(platformProjDir, 'Resources/splash/');
  387. }
  388. return splashScreensDir;
  389. }
  390. function updateSplashScreens (cordovaProject, locations) {
  391. var splashScreens = cordovaProject.projectConfig.getSplashScreens('ios');
  392. if (splashScreens.length === 0) {
  393. events.emit('verbose', 'This app does not have splash screens defined');
  394. return;
  395. }
  396. var platformProjDir = path.relative(cordovaProject.root, locations.xcodeCordovaProj);
  397. var splashScreensDir = getSplashScreensDir(cordovaProject.root, platformProjDir);
  398. var resourceMap = mapSplashScreenResources(splashScreens, splashScreensDir);
  399. events.emit('verbose', 'Updating splash screens at ' + splashScreensDir);
  400. FileUpdater.updatePaths(
  401. resourceMap, { rootDir: cordovaProject.root }, logFileOp);
  402. }
  403. function cleanSplashScreens (projectRoot, projectConfig, locations) {
  404. var splashScreens = projectConfig.getSplashScreens('ios');
  405. if (splashScreens.length > 0) {
  406. var platformProjDir = path.relative(projectRoot, locations.xcodeCordovaProj);
  407. var splashScreensDir = getSplashScreensDir(projectRoot, platformProjDir);
  408. var resourceMap = mapIconResources(splashScreens, splashScreensDir);
  409. Object.keys(resourceMap).forEach(function (targetSplashPath) {
  410. resourceMap[targetSplashPath] = null;
  411. });
  412. events.emit('verbose', 'Cleaning splash screens at ' + splashScreensDir);
  413. // Source paths are removed from the map, so updatePaths() will delete the target files.
  414. FileUpdater.updatePaths(
  415. resourceMap, { rootDir: projectRoot, all: true }, logFileOp);
  416. }
  417. }
  418. function updateFileResources (cordovaProject, locations) {
  419. const platformDir = path.relative(cordovaProject.root, locations.root);
  420. const files = cordovaProject.projectConfig.getFileResources('ios');
  421. const project = projectFile.parse(locations);
  422. // if there are resource-file elements in config.xml
  423. if (files.length === 0) {
  424. events.emit('verbose', 'This app does not have additional resource files defined');
  425. return;
  426. }
  427. let resourceMap = {};
  428. files.forEach(function (res) {
  429. let src = res.src;
  430. let target = res.target;
  431. if (!target) {
  432. target = src;
  433. }
  434. let targetPath = path.join(project.resources_dir, target);
  435. targetPath = path.relative(cordovaProject.root, targetPath);
  436. if (!fs.existsSync(targetPath)) {
  437. project.xcode.addResourceFile(target);
  438. } else {
  439. events.emit('warn', 'Overwriting existing resource file at ' + targetPath);
  440. }
  441. resourceMap[targetPath] = src;
  442. });
  443. events.emit('verbose', 'Updating resource files at ' + platformDir);
  444. FileUpdater.updatePaths(
  445. resourceMap, { rootDir: cordovaProject.root }, logFileOp);
  446. project.write();
  447. }
  448. function cleanFileResources (projectRoot, projectConfig, locations) {
  449. const platformDir = path.relative(projectRoot, locations.root);
  450. const files = projectConfig.getFileResources('ios', true);
  451. if (files.length > 0) {
  452. events.emit('verbose', 'Cleaning resource files at ' + platformDir);
  453. const project = projectFile.parse(locations);
  454. var resourceMap = {};
  455. files.forEach(function (res) {
  456. let src = res.src;
  457. let target = res.target;
  458. if (!target) {
  459. target = src;
  460. }
  461. let targetPath = path.join(project.resources_dir, target);
  462. targetPath = path.relative(projectRoot, targetPath);
  463. const resfile = path.join('Resources', path.basename(targetPath));
  464. project.xcode.removeResourceFile(resfile);
  465. resourceMap[targetPath] = null;
  466. });
  467. FileUpdater.updatePaths(
  468. resourceMap, { rootDir: projectRoot, all: true }, logFileOp);
  469. project.write();
  470. }
  471. }
  472. /**
  473. * Returns an array of images for each possible idiom, scale, and size class. The images themselves are
  474. * located in the platform's splash images by their pattern (@scale~idiom~sizesize). All possible
  475. * combinations are returned, but not all will have a `filename` property. If the latter isn't present,
  476. * the device won't attempt to load an image matching the same traits. If the filename is present,
  477. * the device will try to load the image if it corresponds to the traits.
  478. *
  479. * The resulting return looks like this:
  480. *
  481. * [
  482. * {
  483. * idiom: 'universal|ipad|iphone',
  484. * scale: '1x|2x|3x',
  485. * width: 'any|com',
  486. * height: 'any|com',
  487. * filename: undefined|'Default@scale~idiom~widthheight.png',
  488. * src: undefined|'path/to/original/matched/image/from/splash/screens.png',
  489. * target: undefined|'path/to/asset/library/Default@scale~idiom~widthheight.png'
  490. * }, ...
  491. * ]
  492. *
  493. * @param {Array<Object>} splashScreens splash screens as defined in config.xml for this platform
  494. * @param {string} launchStoryboardImagesDir project-root/Images.xcassets/LaunchStoryboard.imageset/
  495. * @return {Array<Object>}
  496. */
  497. function mapLaunchStoryboardContents (splashScreens, launchStoryboardImagesDir) {
  498. var platformLaunchStoryboardImages = [];
  499. var idioms = ['universal', 'ipad', 'iphone'];
  500. var scalesForIdiom = {
  501. universal: ['1x', '2x', '3x'],
  502. ipad: ['1x', '2x'],
  503. iphone: ['1x', '2x', '3x']
  504. };
  505. var sizes = ['com', 'any'];
  506. idioms.forEach(function (idiom) {
  507. scalesForIdiom[idiom].forEach(function (scale) {
  508. sizes.forEach(function (width) {
  509. sizes.forEach(function (height) {
  510. var item = {
  511. idiom: idiom,
  512. scale: scale,
  513. width: width,
  514. height: height
  515. };
  516. /* examples of the search pattern:
  517. * scale ~ idiom ~ width height
  518. * @2x ~ universal ~ any any
  519. * @3x ~ iphone ~ com any
  520. * @2x ~ ipad ~ com any
  521. */
  522. var searchPattern = '@' + scale + '~' + idiom + '~' + width + height;
  523. /* because old node versions don't have Array.find, the below is
  524. * functionally equivalent to this:
  525. * var launchStoryboardImage = splashScreens.find(function(item) {
  526. * return item.src.indexOf(searchPattern) >= 0;
  527. * });
  528. */
  529. var launchStoryboardImage = splashScreens.reduce(function (p, c) {
  530. return (c.src.indexOf(searchPattern) >= 0) ? c : p;
  531. }, undefined);
  532. if (launchStoryboardImage) {
  533. item.filename = 'Default' + searchPattern + '.png';
  534. item.src = launchStoryboardImage.src;
  535. item.target = path.join(launchStoryboardImagesDir, item.filename);
  536. }
  537. platformLaunchStoryboardImages.push(item);
  538. });
  539. });
  540. });
  541. });
  542. return platformLaunchStoryboardImages;
  543. }
  544. /**
  545. * Returns a dictionary representing the source and destination paths for the launch storyboard images
  546. * that need to be copied.
  547. *
  548. * The resulting return looks like this:
  549. *
  550. * {
  551. * 'target-path': 'source-path',
  552. * ...
  553. * }
  554. *
  555. * @param {Array<Object>} splashScreens splash screens as defined in config.xml for this platform
  556. * @param {string} launchStoryboardImagesDir project-root/Images.xcassets/LaunchStoryboard.imageset/
  557. * @return {Object}
  558. */
  559. function mapLaunchStoryboardResources (splashScreens, launchStoryboardImagesDir) {
  560. var platformLaunchStoryboardImages = mapLaunchStoryboardContents(splashScreens, launchStoryboardImagesDir);
  561. var pathMap = {};
  562. platformLaunchStoryboardImages.forEach(function (item) {
  563. if (item.target) {
  564. pathMap[item.target] = item.src;
  565. }
  566. });
  567. return pathMap;
  568. }
  569. /**
  570. * Builds the object that represents the contents.json file for the LaunchStoryboard image set.
  571. *
  572. * The resulting return looks like this:
  573. *
  574. * {
  575. * images: [
  576. * {
  577. * idiom: 'universal|ipad|iphone',
  578. * scale: '1x|2x|3x',
  579. * width-class: undefined|'compact',
  580. * height-class: undefined|'compact'
  581. * }, ...
  582. * ],
  583. * info: {
  584. * author: 'Xcode',
  585. * version: 1
  586. * }
  587. * }
  588. *
  589. * A bit of minor logic is used to map from the array of images returned from mapLaunchStoryboardContents
  590. * to the format requried by Xcode.
  591. *
  592. * @param {Array<Object>} splashScreens splash screens as defined in config.xml for this platform
  593. * @param {string} launchStoryboardImagesDir project-root/Images.xcassets/LaunchStoryboard.imageset/
  594. * @return {Object}
  595. */
  596. function getLaunchStoryboardContentsJSON (splashScreens, launchStoryboardImagesDir) {
  597. var platformLaunchStoryboardImages = mapLaunchStoryboardContents(splashScreens, launchStoryboardImagesDir);
  598. var contentsJSON = {
  599. images: [],
  600. info: {
  601. author: 'Xcode',
  602. version: 1
  603. }
  604. };
  605. contentsJSON.images = platformLaunchStoryboardImages.map(function (item) {
  606. var newItem = {
  607. idiom: item.idiom,
  608. scale: item.scale
  609. };
  610. // Xcode doesn't want any size class property if the class is "any"
  611. // If our size class is "com", Xcode wants "compact".
  612. if (item.width !== CDV_ANY_SIZE_CLASS) {
  613. newItem['width-class'] = IMAGESET_COMPACT_SIZE_CLASS;
  614. }
  615. if (item.height !== CDV_ANY_SIZE_CLASS) {
  616. newItem['height-class'] = IMAGESET_COMPACT_SIZE_CLASS;
  617. }
  618. // Xcode doesn't want a filename property if there's no image for these traits
  619. if (item.filename) {
  620. newItem.filename = item.filename;
  621. }
  622. return newItem;
  623. });
  624. return contentsJSON;
  625. }
  626. /**
  627. * Determines if the project's build settings may need to be updated for launch storyboard support
  628. *
  629. */
  630. function checkIfBuildSettingsNeedUpdatedForLaunchStoryboard (platformConfig, infoPlist) {
  631. var hasLaunchStoryboardImages = platformHasLaunchStoryboardImages(platformConfig);
  632. var hasLegacyLaunchImages = platformHasLegacyLaunchImages(platformConfig);
  633. var currentLaunchStoryboard = infoPlist[UI_LAUNCH_STORYBOARD_NAME];
  634. if (hasLaunchStoryboardImages && currentLaunchStoryboard === CDV_LAUNCH_STORYBOARD_NAME && !hasLegacyLaunchImages) {
  635. // don't need legacy launch images if we are using our launch storyboard
  636. // so we do need to update the project file
  637. events.emit('verbose', 'Need to update build settings because project is using our launch storyboard.');
  638. return true;
  639. } else if (hasLegacyLaunchImages && !currentLaunchStoryboard) {
  640. // we do need to ensure legacy launch images are used if there's no launch storyboard present
  641. // so we do need to update the project file
  642. events.emit('verbose', 'Need to update build settings because project is using legacy launch images and no storyboard.');
  643. return true;
  644. }
  645. events.emit('verbose', 'No need to update build settings for launch storyboard support.');
  646. return false;
  647. }
  648. function updateBuildSettingsForLaunchStoryboard (proj, platformConfig, infoPlist) {
  649. var hasLaunchStoryboardImages = platformHasLaunchStoryboardImages(platformConfig);
  650. var hasLegacyLaunchImages = platformHasLegacyLaunchImages(platformConfig);
  651. var currentLaunchStoryboard = infoPlist[UI_LAUNCH_STORYBOARD_NAME];
  652. if (hasLaunchStoryboardImages && currentLaunchStoryboard === CDV_LAUNCH_STORYBOARD_NAME && !hasLegacyLaunchImages) {
  653. // don't need legacy launch images if we are using our launch storyboard
  654. events.emit('verbose', 'Removed ' + LAUNCHIMAGE_BUILD_SETTING + ' because project is using our launch storyboard.');
  655. proj.removeBuildProperty(LAUNCHIMAGE_BUILD_SETTING);
  656. } else if (hasLegacyLaunchImages && !currentLaunchStoryboard) {
  657. // we do need to ensure legacy launch images are used if there's no launch storyboard present
  658. events.emit('verbose', 'Set ' + LAUNCHIMAGE_BUILD_SETTING + ' to ' + LAUNCHIMAGE_BUILD_SETTING_VALUE + ' because project is using legacy launch images and no storyboard.');
  659. proj.updateBuildProperty(LAUNCHIMAGE_BUILD_SETTING, LAUNCHIMAGE_BUILD_SETTING_VALUE);
  660. } else {
  661. events.emit('verbose', 'Did not update build settings for launch storyboard support.');
  662. }
  663. }
  664. function splashScreensHaveLaunchStoryboardImages (contentsJSON) {
  665. /* do we have any launch images do we have for our launch storyboard?
  666. * Again, for old Node versions, the below code is equivalent to this:
  667. * return !!contentsJSON.images.find(function (item) {
  668. * return item.filename !== undefined;
  669. * });
  670. */
  671. return !!contentsJSON.images.reduce(function (p, c) {
  672. return (c.filename !== undefined) ? c : p;
  673. }, undefined);
  674. }
  675. function platformHasLaunchStoryboardImages (platformConfig) {
  676. var splashScreens = platformConfig.getSplashScreens('ios');
  677. var contentsJSON = getLaunchStoryboardContentsJSON(splashScreens, ''); // note: we don't need a file path here; we're just counting
  678. return splashScreensHaveLaunchStoryboardImages(contentsJSON);
  679. }
  680. function platformHasLegacyLaunchImages (platformConfig) {
  681. var splashScreens = platformConfig.getSplashScreens('ios');
  682. return !!splashScreens.reduce(function (p, c) {
  683. return (c.width !== undefined || c.height !== undefined) ? c : p;
  684. }, undefined);
  685. }
  686. /**
  687. * Updates the project's plist based upon our launch storyboard images. If there are no images, then we should
  688. * fall back to the regular launch images that might be supplied (that is, our app will be scaled on an iPad Pro),
  689. * and if there are some images, we need to alter the UILaunchStoryboardName property to point to
  690. * CDVLaunchScreen.
  691. *
  692. * There's some logic here to avoid overwriting changes the user might have made to their plist if they are using
  693. * their own launch storyboard.
  694. */
  695. function updateProjectPlistForLaunchStoryboard (platformConfig, infoPlist) {
  696. var currentLaunchStoryboard = infoPlist[UI_LAUNCH_STORYBOARD_NAME];
  697. events.emit('verbose', 'Current launch storyboard ' + currentLaunchStoryboard);
  698. var hasLaunchStoryboardImages = platformHasLaunchStoryboardImages(platformConfig);
  699. if (hasLaunchStoryboardImages && !currentLaunchStoryboard) {
  700. // only change the launch storyboard if we have images to use AND the current value is blank
  701. // if it's not blank, we've either done this before, or the user has their own launch storyboard
  702. events.emit('verbose', 'Changing info plist to use our launch storyboard');
  703. infoPlist[UI_LAUNCH_STORYBOARD_NAME] = CDV_LAUNCH_STORYBOARD_NAME;
  704. return;
  705. }
  706. if (!hasLaunchStoryboardImages && currentLaunchStoryboard === CDV_LAUNCH_STORYBOARD_NAME) {
  707. // only revert to using the launch images if we have don't have any images for the launch storyboard
  708. // but only clear it if current launch storyboard is our storyboard; the user might be using their
  709. // own storyboard instead.
  710. events.emit('verbose', 'Changing info plist to use legacy launch images');
  711. delete infoPlist[UI_LAUNCH_STORYBOARD_NAME];
  712. return;
  713. }
  714. events.emit('verbose', 'Not changing launch storyboard setting in info plist.');
  715. }
  716. /**
  717. * Returns the directory for the Launch Storyboard image set, if image sets are being used. If they aren't
  718. * being used, returns null.
  719. *
  720. * @param {string} projectRoot The project's root directory
  721. * @param {string} platformProjDir The platform's project directory
  722. */
  723. function getLaunchStoryboardImagesDir (projectRoot, platformProjDir) {
  724. var launchStoryboardImagesDir;
  725. var xcassetsExists = folderExists(path.join(projectRoot, platformProjDir, 'Images.xcassets/'));
  726. if (xcassetsExists) {
  727. launchStoryboardImagesDir = path.join(platformProjDir, 'Images.xcassets/LaunchStoryboard.imageset/');
  728. } else {
  729. // if we don't have a asset library for images, we can't do the storyboard.
  730. launchStoryboardImagesDir = null;
  731. }
  732. return launchStoryboardImagesDir;
  733. }
  734. /**
  735. * Update the images for the Launch Storyboard and updates the image set's contents.json file appropriately.
  736. *
  737. * @param {Object} cordovaProject The cordova project
  738. * @param {Object} locations A dictionary containing useful location paths
  739. */
  740. function updateLaunchStoryboardImages (cordovaProject, locations) {
  741. var splashScreens = cordovaProject.projectConfig.getSplashScreens('ios');
  742. var platformProjDir = path.relative(cordovaProject.root, locations.xcodeCordovaProj);
  743. var launchStoryboardImagesDir = getLaunchStoryboardImagesDir(cordovaProject.root, platformProjDir);
  744. if (launchStoryboardImagesDir) {
  745. var resourceMap = mapLaunchStoryboardResources(splashScreens, launchStoryboardImagesDir);
  746. var contentsJSON = getLaunchStoryboardContentsJSON(splashScreens, launchStoryboardImagesDir);
  747. events.emit('verbose', 'Updating launch storyboard images at ' + launchStoryboardImagesDir);
  748. FileUpdater.updatePaths(
  749. resourceMap, { rootDir: cordovaProject.root }, logFileOp);
  750. events.emit('verbose', 'Updating Storyboard image set contents.json');
  751. fs.writeFileSync(path.join(cordovaProject.root, launchStoryboardImagesDir, 'Contents.json'),
  752. JSON.stringify(contentsJSON, null, 2));
  753. }
  754. }
  755. /**
  756. * Removes the images from the launch storyboard's image set and updates the image set's contents.json
  757. * file appropriately.
  758. *
  759. * @param {string} projectRoot Path to the project root
  760. * @param {Object} projectConfig The project's config.xml
  761. * @param {Object} locations A dictionary containing useful location paths
  762. */
  763. function cleanLaunchStoryboardImages (projectRoot, projectConfig, locations) {
  764. var splashScreens = projectConfig.getSplashScreens('ios');
  765. var platformProjDir = path.relative(projectRoot, locations.xcodeCordovaProj);
  766. var launchStoryboardImagesDir = getLaunchStoryboardImagesDir(projectRoot, platformProjDir);
  767. if (launchStoryboardImagesDir) {
  768. var resourceMap = mapLaunchStoryboardResources(splashScreens, launchStoryboardImagesDir);
  769. var contentsJSON = getLaunchStoryboardContentsJSON(splashScreens, launchStoryboardImagesDir);
  770. Object.keys(resourceMap).forEach(function (targetPath) {
  771. resourceMap[targetPath] = null;
  772. });
  773. events.emit('verbose', 'Cleaning storyboard image set at ' + launchStoryboardImagesDir);
  774. // Source paths are removed from the map, so updatePaths() will delete the target files.
  775. FileUpdater.updatePaths(
  776. resourceMap, { rootDir: projectRoot, all: true }, logFileOp);
  777. // delete filename from contents.json
  778. contentsJSON.images.forEach(function (image) {
  779. image.filename = undefined;
  780. });
  781. events.emit('verbose', 'Updating Storyboard image set contents.json');
  782. fs.writeFileSync(path.join(projectRoot, launchStoryboardImagesDir, 'Contents.json'),
  783. JSON.stringify(contentsJSON, null, 2));
  784. }
  785. }
  786. /**
  787. * Queries ConfigParser object for the orientation <preference> value. Warns if
  788. * global preference value is not supported by platform.
  789. *
  790. * @param {Object} platformConfig ConfigParser object
  791. *
  792. * @return {String} Global/platform-specific orientation in lower-case
  793. * (or empty string if both are undefined).
  794. */
  795. function getOrientationValue (platformConfig) {
  796. var ORIENTATION_DEFAULT = 'default';
  797. var orientation = platformConfig.getPreference('orientation');
  798. if (!orientation) {
  799. return '';
  800. }
  801. orientation = orientation.toLowerCase();
  802. // Check if the given global orientation is supported
  803. if (['default', 'portrait', 'landscape', 'all'].indexOf(orientation) >= 0) {
  804. return orientation;
  805. }
  806. events.emit('warn', 'Unrecognized value for Orientation preference: ' + orientation +
  807. '. Defaulting to value: ' + ORIENTATION_DEFAULT + '.');
  808. return ORIENTATION_DEFAULT;
  809. }
  810. /*
  811. Parses all <access> and <allow-navigation> entries and consolidates duplicates (for ATS).
  812. Returns an object with a Hostname as the key, and the value an object with properties:
  813. {
  814. Hostname, // String
  815. NSExceptionAllowsInsecureHTTPLoads, // boolean
  816. NSIncludesSubdomains, // boolean
  817. NSExceptionMinimumTLSVersion, // String
  818. NSExceptionRequiresForwardSecrecy, // boolean
  819. NSRequiresCertificateTransparency, // boolean
  820. // the three below _only_ show when the Hostname is '*'
  821. // if any of the three are set, it disables setting NSAllowsArbitraryLoads
  822. // (Apple already enforces this in ATS)
  823. NSAllowsArbitraryLoadsInWebContent, // boolean (default: false)
  824. NSAllowsLocalNetworking, // boolean (default: false)
  825. NSAllowsArbitraryLoadsForMedia, // boolean (default:false)
  826. }
  827. */
  828. function processAccessAndAllowNavigationEntries (config) {
  829. var accesses = config.getAccesses();
  830. var allow_navigations = config.getAllowNavigations();
  831. return allow_navigations
  832. // we concat allow_navigations and accesses, after processing accesses
  833. .concat(accesses.map(function (obj) {
  834. // map accesses to a common key interface using 'href', not origin
  835. obj.href = obj.origin;
  836. delete obj.origin;
  837. return obj;
  838. }))
  839. // we reduce the array to an object with all the entries processed (key is Hostname)
  840. .reduce(function (previousReturn, currentElement) {
  841. var options = {
  842. minimum_tls_version: currentElement.minimum_tls_version,
  843. requires_forward_secrecy: currentElement.requires_forward_secrecy,
  844. requires_certificate_transparency: currentElement.requires_certificate_transparency,
  845. allows_arbitrary_loads_for_media: currentElement.allows_arbitrary_loads_in_media || currentElement.allows_arbitrary_loads_for_media,
  846. allows_arbitrary_loads_in_web_content: currentElement.allows_arbitrary_loads_in_web_content,
  847. allows_local_networking: currentElement.allows_local_networking
  848. };
  849. var obj = parseWhitelistUrlForATS(currentElement.href, options);
  850. if (obj) {
  851. // we 'union' duplicate entries
  852. var item = previousReturn[obj.Hostname];
  853. if (!item) {
  854. item = {};
  855. }
  856. for (var o in obj) {
  857. if (obj.hasOwnProperty(o)) {
  858. item[o] = obj[o];
  859. }
  860. }
  861. previousReturn[obj.Hostname] = item;
  862. }
  863. return previousReturn;
  864. }, {});
  865. }
  866. /*
  867. Parses a URL and returns an object with these keys:
  868. {
  869. Hostname, // String
  870. NSExceptionAllowsInsecureHTTPLoads, // boolean (default: false)
  871. NSIncludesSubdomains, // boolean (default: false)
  872. NSExceptionMinimumTLSVersion, // String (default: 'TLSv1.2')
  873. NSExceptionRequiresForwardSecrecy, // boolean (default: true)
  874. NSRequiresCertificateTransparency, // boolean (default: false)
  875. // the three below _only_ apply when the Hostname is '*'
  876. // if any of the three are set, it disables setting NSAllowsArbitraryLoads
  877. // (Apple already enforces this in ATS)
  878. NSAllowsArbitraryLoadsInWebContent, // boolean (default: false)
  879. NSAllowsLocalNetworking, // boolean (default: false)
  880. NSAllowsArbitraryLoadsForMedia, // boolean (default:false)
  881. }
  882. null is returned if the URL cannot be parsed, or is to be skipped for ATS.
  883. */
  884. function parseWhitelistUrlForATS (url, options) {
  885. // @todo 'url.parse' was deprecated since v11.0.0. Use 'url.URL' constructor instead.
  886. var href = URL.parse(url); // eslint-disable-line
  887. var retObj = {};
  888. retObj.Hostname = href.hostname;
  889. // Guiding principle: we only set values in retObj if they are NOT the default
  890. if (url === '*') {
  891. retObj.Hostname = '*';
  892. var val;
  893. val = (options.allows_arbitrary_loads_in_web_content === 'true');
  894. if (options.allows_arbitrary_loads_in_web_content && val) { // default is false
  895. retObj.NSAllowsArbitraryLoadsInWebContent = true;
  896. }
  897. val = (options.allows_arbitrary_loads_for_media === 'true');
  898. if (options.allows_arbitrary_loads_for_media && val) { // default is false
  899. retObj.NSAllowsArbitraryLoadsForMedia = true;
  900. }
  901. val = (options.allows_local_networking === 'true');
  902. if (options.allows_local_networking && val) { // default is false
  903. retObj.NSAllowsLocalNetworking = true;
  904. }
  905. return retObj;
  906. }
  907. if (!retObj.Hostname) {
  908. // check origin, if it allows subdomains (wildcard in hostname), we set NSIncludesSubdomains to YES. Default is NO
  909. var subdomain1 = '/*.'; // wildcard in hostname
  910. var subdomain2 = '*://*.'; // wildcard in hostname and protocol
  911. var subdomain3 = '*://'; // wildcard in protocol only
  912. if (!href.pathname) {
  913. return null;
  914. } else if (href.pathname.indexOf(subdomain1) === 0) {
  915. retObj.NSIncludesSubdomains = true;
  916. retObj.Hostname = href.pathname.substring(subdomain1.length);
  917. } else if (href.pathname.indexOf(subdomain2) === 0) {
  918. retObj.NSIncludesSubdomains = true;
  919. retObj.Hostname = href.pathname.substring(subdomain2.length);
  920. } else if (href.pathname.indexOf(subdomain3) === 0) {
  921. retObj.Hostname = href.pathname.substring(subdomain3.length);
  922. } else {
  923. // Handling "scheme:*" case to avoid creating of a blank key in NSExceptionDomains.
  924. return null;
  925. }
  926. }
  927. if (options.minimum_tls_version && options.minimum_tls_version !== 'TLSv1.2') { // default is TLSv1.2
  928. retObj.NSExceptionMinimumTLSVersion = options.minimum_tls_version;
  929. }
  930. var rfs = (options.requires_forward_secrecy === 'true');
  931. if (options.requires_forward_secrecy && !rfs) { // default is true
  932. retObj.NSExceptionRequiresForwardSecrecy = false;
  933. }
  934. var rct = (options.requires_certificate_transparency === 'true');
  935. if (options.requires_certificate_transparency && rct) { // default is false
  936. retObj.NSRequiresCertificateTransparency = true;
  937. }
  938. // if the scheme is HTTP, we set NSExceptionAllowsInsecureHTTPLoads to YES. Default is NO
  939. if (href.protocol === 'http:') {
  940. retObj.NSExceptionAllowsInsecureHTTPLoads = true;
  941. } else if (!href.protocol && href.pathname.indexOf('*:/') === 0) { // wilcard in protocol
  942. retObj.NSExceptionAllowsInsecureHTTPLoads = true;
  943. }
  944. return retObj;
  945. }
  946. /*
  947. App Transport Security (ATS) writer from <access> and <allow-navigation> tags
  948. in config.xml
  949. */
  950. function writeATSEntries (config) {
  951. var pObj = processAccessAndAllowNavigationEntries(config);
  952. var ats = {};
  953. for (var hostname in pObj) {
  954. if (pObj.hasOwnProperty(hostname)) {
  955. var entry = pObj[hostname];
  956. // Guiding principle: we only set values if they are available
  957. if (hostname === '*') {
  958. // always write this, for iOS 9, since in iOS 10 it will be overriden if
  959. // any of the other three keys are written
  960. ats['NSAllowsArbitraryLoads'] = true;
  961. // at least one of the overriding keys is present
  962. if (entry.NSAllowsArbitraryLoadsInWebContent) {
  963. ats['NSAllowsArbitraryLoadsInWebContent'] = true;
  964. }
  965. if (entry.NSAllowsArbitraryLoadsForMedia) {
  966. ats['NSAllowsArbitraryLoadsForMedia'] = true;
  967. }
  968. if (entry.NSAllowsLocalNetworking) {
  969. ats['NSAllowsLocalNetworking'] = true;
  970. }
  971. continue;
  972. }
  973. var exceptionDomain = {};
  974. for (var key in entry) {
  975. if (entry.hasOwnProperty(key) && key !== 'Hostname') {
  976. exceptionDomain[key] = entry[key];
  977. }
  978. }
  979. if (!ats['NSExceptionDomains']) {
  980. ats['NSExceptionDomains'] = {};
  981. }
  982. ats['NSExceptionDomains'][hostname] = exceptionDomain;
  983. }
  984. }
  985. return ats;
  986. }
  987. function folderExists (folderPath) {
  988. try {
  989. var stat = fs.statSync(folderPath);
  990. return stat && stat.isDirectory();
  991. } catch (e) {
  992. return false;
  993. }
  994. }
  995. // Construct a default value for CFBundleVersion as the version with any
  996. // -rclabel stripped=.
  997. function default_CFBundleVersion (version) {
  998. return version.split('-')[0];
  999. }
  1000. // Converts cordova specific representation of target device to XCode value
  1001. function parseTargetDevicePreference (value) {
  1002. if (!value) return null;
  1003. var map = { 'universal': '"1,2"', 'handset': '"1"', 'tablet': '"2"' };
  1004. if (map[value.toLowerCase()]) {
  1005. return map[value.toLowerCase()];
  1006. }
  1007. events.emit('warn', 'Unrecognized value for target-device preference: ' + value + '.');
  1008. return null;
  1009. }