1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const plugin_1 = require("./plugin");
|
4 | const fs_1 = require("./util/fs");
|
5 | const path_1 = require("path");
|
6 | const common_1 = require("./common");
|
7 | const fs_extra_1 = require("fs-extra");
|
8 | const common_2 = require("./android/common");
|
9 | const common_3 = require("./ios/common");
|
10 | const copy_1 = require("./tasks/copy");
|
11 | const inquirer = require("inquirer");
|
12 | const plist = require('plist');
|
13 | const chalk = require('chalk');
|
14 |
|
15 |
|
16 |
|
17 | function generateCordovaPluginsJSFile(config, plugins, platform) {
|
18 | let pluginModules = [];
|
19 | let pluginExports = [];
|
20 | plugins.map((p) => {
|
21 | const pluginId = p.xml.$.id;
|
22 | const jsModules = plugin_1.getJSModules(p, platform);
|
23 | jsModules.map((jsModule) => {
|
24 | let clobbers = [];
|
25 | let merges = [];
|
26 | let clobbersModule = '';
|
27 | let mergesModule = '';
|
28 | let runsModule = '';
|
29 | let clobberKey = '';
|
30 | let mergeKey = '';
|
31 | if (jsModule.clobbers) {
|
32 | jsModule.clobbers.map((clobber) => {
|
33 | clobbers.push(clobber.$.target);
|
34 | clobberKey = clobber.$.target;
|
35 | });
|
36 | clobbersModule = `,
|
37 | "clobbers": [
|
38 | "${clobbers.join('",\n "')}"
|
39 | ]`;
|
40 | }
|
41 | if (jsModule.merges) {
|
42 | jsModule.merges.map((merge) => {
|
43 | merges.push(merge.$.target);
|
44 | mergeKey = merge.$.target;
|
45 | });
|
46 | mergesModule = `,
|
47 | "merges": [
|
48 | "${merges.join('",\n "')}"
|
49 | ]`;
|
50 | }
|
51 | if (jsModule.runs) {
|
52 | runsModule = ',\n "runs": true';
|
53 | }
|
54 | const pluginModule = {
|
55 | clobber: clobberKey,
|
56 | merge: mergeKey,
|
57 |
|
58 | pluginContent: `{
|
59 | "id": "${pluginId + '.' + (jsModule.$.name || jsModule.$.src.match(/([^\/]+)\.js/)[1])}",
|
60 | "file": "plugins/${pluginId}/${jsModule.$.src}",
|
61 | "pluginId": "${pluginId}"${clobbersModule}${mergesModule}${runsModule}
|
62 | }`
|
63 | };
|
64 | pluginModules.push(pluginModule);
|
65 | });
|
66 | pluginExports.push(`"${pluginId}": "${p.xml.$.version}"`);
|
67 | });
|
68 | return `
|
69 | cordova.define('cordova/plugin_list', function(require, exports, module) {
|
70 | module.exports = [
|
71 | ${pluginModules
|
72 | .sort((a, b) => (a.clobber && b.clobber) // Clobbers in alpha order
|
73 | ? a.clobber.localeCompare(b.clobber)
|
74 | : ((a.clobber || b.clobber) // Clobbers before anything else
|
75 | ? b.clobber.localeCompare(a.clobber)
|
76 | : a.merge.localeCompare(b.merge) // Merges in alpha order
|
77 | ))
|
78 | .map(e => e.pluginContent)
|
79 | .join(',\n ')}
|
80 | ];
|
81 | module.exports.metadata =
|
82 | // TOP OF METADATA
|
83 | {
|
84 | ${pluginExports.join(',\n ')}
|
85 | };
|
86 | // BOTTOM OF METADATA
|
87 | });
|
88 | `;
|
89 | }
|
90 | exports.generateCordovaPluginsJSFile = generateCordovaPluginsJSFile;
|
91 |
|
92 |
|
93 |
|
94 | async function copyPluginsJS(config, cordovaPlugins, platform) {
|
95 | const webDir = getWebDir(config, platform);
|
96 | const pluginsDir = path_1.join(webDir, 'plugins');
|
97 | const cordovaPluginsJSFile = path_1.join(webDir, 'cordova_plugins.js');
|
98 | removePluginFiles(config, platform);
|
99 | await Promise.all(cordovaPlugins.map(async (p) => {
|
100 | const pluginId = p.xml.$.id;
|
101 | const pluginDir = path_1.join(pluginsDir, pluginId, 'www');
|
102 | fs_1.ensureDirSync(pluginDir);
|
103 | const jsModules = plugin_1.getJSModules(p, platform);
|
104 | await Promise.all(jsModules.map(async (jsModule) => {
|
105 | const filePath = path_1.join(webDir, 'plugins', pluginId, jsModule.$.src);
|
106 | fs_1.copySync(path_1.join(p.rootPath, jsModule.$.src), filePath);
|
107 | let data = await fs_1.readFileAsync(filePath, 'utf8');
|
108 | data = data.trim();
|
109 |
|
110 | const name = pluginId + '.' + (jsModule.$.name || path_1.basename(jsModule.$.src, path_1.extname(jsModule.$.src)));
|
111 | data = `cordova.define("${name}", function(require, exports, module) { \n${data}\n});`;
|
112 | data = data.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script\s*>/gi, '');
|
113 | await fs_1.writeFileAsync(filePath, data, 'utf8');
|
114 | }));
|
115 | const assets = plugin_1.getAssets(p, platform);
|
116 | assets.map((asset) => {
|
117 | const filePath = path_1.join(webDir, asset.$.target);
|
118 | fs_1.copySync(path_1.join(p.rootPath, asset.$.src), filePath);
|
119 | });
|
120 | }));
|
121 | fs_1.writeFileAsync(cordovaPluginsJSFile, generateCordovaPluginsJSFile(config, cordovaPlugins, platform));
|
122 | }
|
123 | exports.copyPluginsJS = copyPluginsJS;
|
124 | async function copyCordovaJS(config, platform) {
|
125 | const cordovaPath = common_1.resolveNode(config, '@capacitor/core', 'cordova.js');
|
126 | if (!cordovaPath) {
|
127 | common_1.logFatal(`Unable to find node_modules/@capacitor/core/cordova.js. Are you sure`, '@capacitor/core is installed? This file is currently required for Capacitor to function.');
|
128 | return;
|
129 | }
|
130 | return fs_extra_1.copy(cordovaPath, path_1.join(getWebDir(config, platform), 'cordova.js'));
|
131 | }
|
132 | exports.copyCordovaJS = copyCordovaJS;
|
133 | async function createEmptyCordovaJS(config, platform) {
|
134 | await fs_1.writeFileAsync(path_1.join(getWebDir(config, platform), 'cordova.js'), '');
|
135 | await fs_1.writeFileAsync(path_1.join(getWebDir(config, platform), 'cordova_plugins.js'), '');
|
136 | }
|
137 | exports.createEmptyCordovaJS = createEmptyCordovaJS;
|
138 | function removePluginFiles(config, platform) {
|
139 | const webDir = getWebDir(config, platform);
|
140 | const pluginsDir = path_1.join(webDir, 'plugins');
|
141 | const cordovaPluginsJSFile = path_1.join(webDir, 'cordova_plugins.js');
|
142 | fs_1.removeSync(pluginsDir);
|
143 | fs_1.removeSync(cordovaPluginsJSFile);
|
144 | }
|
145 | exports.removePluginFiles = removePluginFiles;
|
146 | async function autoGenerateConfig(config, cordovaPlugins, platform) {
|
147 | let xmlDir = path_1.join(config.android.resDirAbs, 'xml');
|
148 | const fileName = 'config.xml';
|
149 | if (platform === 'ios') {
|
150 | xmlDir = path_1.join(config.ios.platformDir, config.ios.nativeProjectName, config.ios.nativeProjectName);
|
151 | }
|
152 | fs_1.ensureDirSync(xmlDir);
|
153 | const cordovaConfigXMLFile = path_1.join(xmlDir, fileName);
|
154 | fs_1.removeSync(cordovaConfigXMLFile);
|
155 | let pluginEntries = [];
|
156 | cordovaPlugins.map(p => {
|
157 | const currentPlatform = plugin_1.getPluginPlatform(p, platform);
|
158 | if (currentPlatform) {
|
159 | const configFiles = currentPlatform['config-file'];
|
160 | if (configFiles) {
|
161 | const configXMLEntries = configFiles.filter(function (item) { return item.$ && item.$.target.includes(fileName); });
|
162 | configXMLEntries.map((entry) => {
|
163 | if (entry.feature) {
|
164 | const feature = { feature: entry.feature };
|
165 | pluginEntries.push(feature);
|
166 | }
|
167 | });
|
168 | }
|
169 | }
|
170 | });
|
171 | const pluginEntriesString = await Promise.all(pluginEntries.map(async (item) => {
|
172 | const xmlString = await common_1.writeXML(item);
|
173 | return xmlString;
|
174 | }));
|
175 | let pluginPreferencesString = [];
|
176 | if (config.app.extConfig && config.app.extConfig.cordova && config.app.extConfig.cordova.preferences) {
|
177 | pluginPreferencesString = await Promise.all(Object.keys(config.app.extConfig.cordova.preferences).map(async (key) => {
|
178 | return `
|
179 | <preference name="${key}" value="${config.app.extConfig.cordova.preferences[key]}" />`;
|
180 | }));
|
181 | }
|
182 | const content = `<?xml version='1.0' encoding='utf-8'?>
|
183 | <widget version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
|
184 | <access origin="*" />
|
185 | ${pluginEntriesString.join('')}
|
186 | ${pluginPreferencesString.join('')}
|
187 | </widget>`;
|
188 | await fs_1.writeFileAsync(cordovaConfigXMLFile, content);
|
189 | }
|
190 | exports.autoGenerateConfig = autoGenerateConfig;
|
191 | function getWebDir(config, platform) {
|
192 | if (platform === 'ios') {
|
193 | return config.ios.webDirAbs;
|
194 | }
|
195 | if (platform === 'android') {
|
196 | return config.android.webDirAbs;
|
197 | }
|
198 | return '';
|
199 | }
|
200 | async function handleCordovaPluginsJS(cordovaPlugins, config, platform) {
|
201 | if (!fs_extra_1.existsSync(getWebDir(config, platform))) {
|
202 | await copy_1.copy(config, platform);
|
203 | }
|
204 | if (cordovaPlugins.length > 0) {
|
205 | plugin_1.printPlugins(cordovaPlugins, platform, 'cordova');
|
206 | await copyCordovaJS(config, platform);
|
207 | await copyPluginsJS(config, cordovaPlugins, platform);
|
208 | }
|
209 | else {
|
210 | removePluginFiles(config, platform);
|
211 | await createEmptyCordovaJS(config, platform);
|
212 | }
|
213 | await autoGenerateConfig(config, cordovaPlugins, platform);
|
214 | }
|
215 | exports.handleCordovaPluginsJS = handleCordovaPluginsJS;
|
216 | async function getCordovaPlugins(config, platform) {
|
217 | const allPlugins = await plugin_1.getPlugins(config);
|
218 | let plugins = [];
|
219 | if (platform === config.ios.name) {
|
220 | plugins = common_3.getIOSPlugins(allPlugins);
|
221 | }
|
222 | else if (platform === config.android.name) {
|
223 | plugins = common_2.getAndroidPlugins(allPlugins);
|
224 | }
|
225 | return plugins
|
226 | .filter(p => plugin_1.getPluginType(p, platform) === 1 );
|
227 | }
|
228 | exports.getCordovaPlugins = getCordovaPlugins;
|
229 | async function logCordovaManualSteps(cordovaPlugins, config, platform) {
|
230 | cordovaPlugins.map(p => {
|
231 | const editConfig = plugin_1.getPlatformElement(p, platform, 'edit-config');
|
232 | const configFile = plugin_1.getPlatformElement(p, platform, 'config-file');
|
233 | editConfig.concat(configFile).map(async (configElement) => {
|
234 | if (configElement.$ && !configElement.$.target.includes('config.xml')) {
|
235 | if (platform === config.ios.name) {
|
236 | if (configElement.$.target.includes('Info.plist')) {
|
237 | logiOSPlist(configElement, config, p);
|
238 | }
|
239 | }
|
240 | }
|
241 | });
|
242 | });
|
243 | }
|
244 | exports.logCordovaManualSteps = logCordovaManualSteps;
|
245 | async function logiOSPlist(configElement, config, plugin) {
|
246 | const plistPath = path_1.resolve(config.ios.platformDir, config.ios.nativeProjectName, config.ios.nativeProjectName, 'Info.plist');
|
247 | const xmlMeta = await common_1.readXML(plistPath);
|
248 | let data = await fs_1.readFileAsync(plistPath, 'utf8');
|
249 | var plistData = plist.parse(data);
|
250 | const dict = xmlMeta.plist.dict.pop();
|
251 | if (!dict.key.includes(configElement.$.parent)) {
|
252 | let xml = buildConfigFileXml(configElement);
|
253 | xml = `<key>${configElement.$.parent}</key>${getConfigFileTagContent(xml)}`;
|
254 | common_1.logWarn(`Plugin ${plugin.id} requires you to add \n ${xml} to your Info.plist to work`);
|
255 | }
|
256 | else if (configElement.array || configElement.dict) {
|
257 | if (configElement.array && configElement.array[0] && configElement.array[0].string) {
|
258 | var xml = '';
|
259 | configElement.array[0].string.map((element) => {
|
260 | if (!plistData[configElement.$.parent].includes(element)) {
|
261 | xml = xml.concat(`<string>${element}</string>\n`);
|
262 | }
|
263 | });
|
264 | if (xml.length > 0) {
|
265 | common_1.logWarn(`Plugin ${plugin.id} requires you to add \n${xml} in the existing ${chalk.bold(configElement.$.parent)} array of your Info.plist to work`);
|
266 | }
|
267 | }
|
268 | else {
|
269 | logPossibleMissingItem(configElement, plugin);
|
270 | }
|
271 | }
|
272 | }
|
273 | function logPossibleMissingItem(configElement, plugin) {
|
274 | let xml = buildConfigFileXml(configElement);
|
275 | xml = getConfigFileTagContent(xml);
|
276 | xml = removeOuterTags(xml);
|
277 | common_1.logWarn(`Plugin ${plugin.id} might require you to add ${xml} in the existing ${chalk.bold(configElement.$.parent)} entry of your Info.plist to work`);
|
278 | }
|
279 | function buildConfigFileXml(configElement) {
|
280 | return common_1.buildXmlElement(configElement, 'config-file');
|
281 | }
|
282 | function getConfigFileTagContent(str) {
|
283 | return str.replace(/\<config-file.+\"\>|\<\/config-file>/g, '');
|
284 | }
|
285 | function removeOuterTags(str) {
|
286 | var start = str.indexOf('>') + 1;
|
287 | var end = str.lastIndexOf('<');
|
288 | return str.substring(start, end);
|
289 | }
|
290 | async function checkAndInstallDependencies(config, plugins, platform) {
|
291 | let needsUpdate = false;
|
292 | const cordovaPlugins = plugins
|
293 | .filter(p => plugin_1.getPluginType(p, platform) === 1 );
|
294 | const incompatible = plugins.filter(p => plugin_1.getPluginType(p, platform) === 2 );
|
295 | await Promise.all(cordovaPlugins.map(async (p) => {
|
296 | let allDependencies = [];
|
297 | allDependencies = allDependencies.concat(plugin_1.getPlatformElement(p, platform, 'dependency'));
|
298 | if (p.xml['dependency']) {
|
299 | allDependencies = allDependencies.concat(p.xml['dependency']);
|
300 | }
|
301 | allDependencies = allDependencies.filter((dep) => !getIncompatibleCordovaPlugins(platform).includes(dep.$.id) && incompatible.filter(p => p.id === dep.$.id || p.xml.$.id === dep.$.id).length === 0);
|
302 | if (allDependencies) {
|
303 | await Promise.all(allDependencies.map(async (dep) => {
|
304 | let plugin = dep.$.id;
|
305 | if (plugin.includes('@') && plugin.indexOf('@') !== 0) {
|
306 | plugin = plugin.split('@')[0];
|
307 | }
|
308 | if (cordovaPlugins.filter(p => p.id === plugin || p.xml.$.id === plugin).length === 0) {
|
309 | if (dep.$.url && dep.$.url.startsWith('http')) {
|
310 | plugin = dep.$.url;
|
311 | }
|
312 | common_1.logInfo(`installing missing dependency plugin ${plugin}`);
|
313 | try {
|
314 | await common_1.installDeps(config.app.rootDir, [plugin], config);
|
315 | await config.updateAppPackage();
|
316 | needsUpdate = true;
|
317 | }
|
318 | catch (e) {
|
319 | common_1.log('\n');
|
320 | common_1.logError(`couldn't install dependency plugin ${plugin}`);
|
321 | }
|
322 | }
|
323 | }));
|
324 | }
|
325 | }));
|
326 | return needsUpdate;
|
327 | }
|
328 | exports.checkAndInstallDependencies = checkAndInstallDependencies;
|
329 | function getIncompatibleCordovaPlugins(platform) {
|
330 | let pluginList = ['cordova-plugin-splashscreen', 'cordova-plugin-ionic-webview', 'cordova-plugin-crosswalk-webview',
|
331 | 'cordova-plugin-wkwebview-engine', 'cordova-plugin-console', 'cordova-plugin-music-controls',
|
332 | 'cordova-plugin-add-swift-support', 'cordova-plugin-ionic-keyboard', 'cordova-plugin-braintree',
|
333 | '@ionic-enterprise/filesystem', '@ionic-enterprise/keyboard', '@ionic-enterprise/splashscreen', 'cordova-support-google-services'];
|
334 | if (platform === 'ios') {
|
335 | pluginList.push('cordova-plugin-statusbar', '@ionic-enterprise/statusbar');
|
336 | }
|
337 | if (platform === 'android') {
|
338 | pluginList.push('cordova-plugin-compat');
|
339 | }
|
340 | return pluginList;
|
341 | }
|
342 | exports.getIncompatibleCordovaPlugins = getIncompatibleCordovaPlugins;
|
343 | async function getCordovaPreferences(config) {
|
344 | const configXml = path_1.join(config.app.rootDir, 'config.xml');
|
345 | let cordova = {};
|
346 | if (fs_extra_1.existsSync(configXml)) {
|
347 | cordova.preferences = {};
|
348 | const xmlMeta = await common_1.readXML(configXml);
|
349 | if (xmlMeta.widget.preference) {
|
350 | xmlMeta.widget.preference.map((pref) => {
|
351 | cordova.preferences[pref.$.name] = pref.$.value;
|
352 | });
|
353 | }
|
354 | }
|
355 | if (config.app.extConfig && config.app.extConfig.cordova && config.app.extConfig.cordova.preferences && cordova.preferences) {
|
356 | const answer = await inquirer.prompt({
|
357 | type: 'confirm',
|
358 | name: 'confirm',
|
359 | message: 'capacitor.config.json already contains cordova preferences. Overwrite with values from config.xml?'
|
360 | });
|
361 | if (!answer.confirm) {
|
362 | cordova = config.app.extConfig.cordova;
|
363 | }
|
364 | }
|
365 | if (config.app.extConfig && !cordova.preferences) {
|
366 | cordova = config.app.extConfig.cordova;
|
367 | }
|
368 | return cordova;
|
369 | }
|
370 | exports.getCordovaPreferences = getCordovaPreferences;
|
371 | async function writeCordovaAndroidManifest(cordovaPlugins, config, platform) {
|
372 | var _a;
|
373 | const pluginsFolder = path_1.resolve(config.app.rootDir, 'android', config.android.assets.pluginsFolderName);
|
374 | const manifestPath = path_1.join(pluginsFolder, 'src', 'main', 'AndroidManifest.xml');
|
375 | let rootXMLEntries = [];
|
376 | let applicationXMLEntries = [];
|
377 | let applicationXMLAttributes = [];
|
378 | let prefsArray = [];
|
379 | cordovaPlugins.map(async (p) => {
|
380 | const editConfig = plugin_1.getPlatformElement(p, platform, 'edit-config');
|
381 | const configFile = plugin_1.getPlatformElement(p, platform, 'config-file');
|
382 | prefsArray = prefsArray.concat(plugin_1.getAllElements(p, platform, 'preference'));
|
383 | editConfig.concat(configFile).map(async (configElement) => {
|
384 | if (configElement.$ && (configElement.$.target && configElement.$.target.includes('AndroidManifest.xml') || configElement.$.file && configElement.$.file.includes('AndroidManifest.xml'))) {
|
385 | const keys = Object.keys(configElement).filter(k => k !== '$');
|
386 | keys.map(k => {
|
387 | configElement[k].map((e) => {
|
388 | const xmlElement = common_1.buildXmlElement(e, k);
|
389 | const pathParts = getPathParts(configElement.$.parent || configElement.$.target);
|
390 | if (pathParts.length > 1) {
|
391 | if (pathParts.pop() === 'application') {
|
392 | if (configElement.$.mode && configElement.$.mode === 'merge' && xmlElement.startsWith('<application')) {
|
393 | Object.keys(e.$).map((ek) => {
|
394 | applicationXMLAttributes.push(`${ek}="${e.$[ek]}"`);
|
395 | });
|
396 | }
|
397 | else if (!applicationXMLEntries.includes(xmlElement) && !contains(applicationXMLEntries, xmlElement, k)) {
|
398 | applicationXMLEntries.push(xmlElement);
|
399 | }
|
400 | }
|
401 | else {
|
402 | common_1.logInfo(`plugin ${p.id} requires to add \n ${xmlElement} to your AndroidManifest.xml to work`);
|
403 | }
|
404 | }
|
405 | else {
|
406 | if (!rootXMLEntries.includes(xmlElement) && !contains(rootXMLEntries, xmlElement, k)) {
|
407 | rootXMLEntries.push(xmlElement);
|
408 | }
|
409 | }
|
410 | });
|
411 | });
|
412 | }
|
413 | });
|
414 | });
|
415 | let cleartextString = 'android:usesCleartextTraffic="true"';
|
416 | let cleartext = ((_a = config.app.extConfig.server) === null || _a === void 0 ? void 0 : _a.cleartext) && !applicationXMLAttributes.includes(cleartextString) ? cleartextString : '';
|
417 | let content = `<?xml version='1.0' encoding='utf-8'?>
|
418 | <manifest package="capacitor.android.plugins"
|
419 | xmlns:android="http://schemas.android.com/apk/res/android"
|
420 | xmlns:amazon="http://schemas.amazon.com/apk/res/android">
|
421 | <application ${applicationXMLAttributes.join('\n')} ${cleartext}>
|
422 | ${applicationXMLEntries.join('\n')}
|
423 | </application>
|
424 | ${rootXMLEntries.join('\n')}
|
425 | </manifest>`;
|
426 | content = content.replace(new RegExp(('$PACKAGE_NAME').replace('$', '\\$&'), 'g'), '${applicationId}');
|
427 | prefsArray.map((preference) => {
|
428 | content = content.replace(new RegExp(('$' + preference.$.name).replace('$', '\\$&'), 'g'), preference.$.default);
|
429 | });
|
430 | if (fs_extra_1.existsSync(manifestPath)) {
|
431 | await fs_1.writeFileAsync(manifestPath, content);
|
432 | }
|
433 | }
|
434 | exports.writeCordovaAndroidManifest = writeCordovaAndroidManifest;
|
435 | function getPathParts(path) {
|
436 | const rootPath = 'manifest';
|
437 | path = path.replace('/*', rootPath);
|
438 | let parts = path.split('/').filter(part => part !== '');
|
439 | if (parts.length > 1 || parts.includes(rootPath)) {
|
440 | return parts;
|
441 | }
|
442 | return [rootPath, path];
|
443 | }
|
444 | function contains(a, obj, k) {
|
445 | const element = common_1.parseXML(obj);
|
446 | for (var i = 0; i < a.length; i++) {
|
447 | const current = common_1.parseXML(a[i]);
|
448 | if (element && current && current[k] && element[k] && current[k].$ && element[k].$ && element[k].$['android:name'] === current[k].$['android:name']) {
|
449 | return true;
|
450 | }
|
451 | }
|
452 | return false;
|
453 | }
|