@thathoff/cordova-plugin-universal-links 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +84 -0
- package/LICENSE +22 -0
- package/README.md +908 -0
- package/docs/images/app-associated-domains.jpg +0 -0
- package/docs/images/app-id-team-prefix.jpg +0 -0
- package/docs/images/app-id.jpg +0 -0
- package/docs/images/branch-io-link-domain.jpg +0 -0
- package/docs/images/branch-io.jpg +0 -0
- package/docs/images/developer-console.jpg +0 -0
- package/hooks/afterPrepareHook.js +86 -0
- package/hooks/beforePluginInstallHook.js +55 -0
- package/hooks/iosBeforePrepareHook.js +74 -0
- package/hooks/lib/android/manifestWriter.js +320 -0
- package/hooks/lib/android/webSiteHook.js +162 -0
- package/hooks/lib/configXmlHelper.js +117 -0
- package/hooks/lib/configXmlParser.js +145 -0
- package/hooks/lib/ios/appleAppSiteAssociationFile.js +173 -0
- package/hooks/lib/ios/projectEntitlements.js +174 -0
- package/hooks/lib/ios/xcodePreferences.js +243 -0
- package/hooks/lib/xmlHelper.js +57 -0
- package/package.json +58 -0
- package/plugin.xml +106 -0
- package/src/android/com/nordnetab/cordova/ul/UniversalLinksPlugin.java +221 -0
- package/src/android/com/nordnetab/cordova/ul/js/JSAction.java +19 -0
- package/src/android/com/nordnetab/cordova/ul/model/JSMessage.java +193 -0
- package/src/android/com/nordnetab/cordova/ul/model/ULHost.java +85 -0
- package/src/android/com/nordnetab/cordova/ul/model/ULPath.java +43 -0
- package/src/android/com/nordnetab/cordova/ul/parser/ULConfigXmlParser.java +156 -0
- package/src/android/com/nordnetab/cordova/ul/parser/XmlTags.java +49 -0
- package/src/ios/AppDelegate+CULPlugin.h +17 -0
- package/src/ios/AppDelegate+CULPlugin.m +32 -0
- package/src/ios/CULPlugin.h +38 -0
- package/src/ios/CULPlugin.m +176 -0
- package/src/ios/JS/CDVInvokedUrlCommand+CULPlugin.h +21 -0
- package/src/ios/JS/CDVInvokedUrlCommand+CULPlugin.m +19 -0
- package/src/ios/JS/CDVPluginResult+CULPlugin.h +29 -0
- package/src/ios/JS/CDVPluginResult+CULPlugin.m +157 -0
- package/src/ios/Model/CULHost.h +63 -0
- package/src/ios/Model/CULHost.m +50 -0
- package/src/ios/Model/CULPath.h +36 -0
- package/src/ios/Model/CULPath.m +21 -0
- package/src/ios/Parser/JSON/CULConfigJsonParser.h +22 -0
- package/src/ios/Parser/JSON/CULConfigJsonParser.m +60 -0
- package/src/ios/Parser/XML/CULConfigXmlParser.h +22 -0
- package/src/ios/Parser/XML/CULConfigXmlParser.m +123 -0
- package/src/ios/Parser/XML/CULXmlTags.h +54 -0
- package/src/ios/Parser/XML/CULXmlTags.m +22 -0
- package/src/ios/Utils/NSBundle+CULPlugin.h +22 -0
- package/src/ios/Utils/NSBundle+CULPlugin.m +15 -0
- package/ul_web_hooks/android_web_hook_tpl.html +23 -0
- package/www/universal_links.js +56 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Hook is executed at the end of the 'prepare' stage. Usually, when you call 'cordova build'.
|
|
3
|
+
|
|
4
|
+
It will inject required preferences in the platform-specific projects, based on <universal-links>
|
|
5
|
+
data you have specified in the projects config.xml file.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
var configParser = require('./lib/configXmlParser.js');
|
|
9
|
+
var androidManifestWriter = require('./lib/android/manifestWriter.js');
|
|
10
|
+
var androidWebHook = require('./lib/android/webSiteHook.js');
|
|
11
|
+
var iosProjectEntitlements = require('./lib/ios/projectEntitlements.js');
|
|
12
|
+
var iosAppSiteAssociationFile = require('./lib/ios/appleAppSiteAssociationFile.js');
|
|
13
|
+
var iosProjectPreferences = require('./lib/ios/xcodePreferences.js');
|
|
14
|
+
var ANDROID = 'android';
|
|
15
|
+
var IOS = 'ios';
|
|
16
|
+
|
|
17
|
+
module.exports = function(ctx) {
|
|
18
|
+
run(ctx);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Execute hook.
|
|
23
|
+
*
|
|
24
|
+
* @param {Object} cordovaContext - cordova context object
|
|
25
|
+
*/
|
|
26
|
+
function run(cordovaContext) {
|
|
27
|
+
var pluginPreferences = configParser.readPreferences(cordovaContext);
|
|
28
|
+
var platformsList = cordovaContext.opts.platforms;
|
|
29
|
+
|
|
30
|
+
// if no preferences are found - exit
|
|
31
|
+
if (pluginPreferences == null) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// if no host is defined - exit
|
|
36
|
+
if (pluginPreferences.hosts == null || pluginPreferences.hosts.length == 0) {
|
|
37
|
+
console.warn('No host is specified in the config.xml. Universal Links plugin is not going to work.');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
platformsList.forEach(function(platform) {
|
|
42
|
+
switch (platform) {
|
|
43
|
+
case ANDROID:
|
|
44
|
+
{
|
|
45
|
+
activateUniversalLinksInAndroid(cordovaContext, pluginPreferences);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case IOS:
|
|
49
|
+
{
|
|
50
|
+
activateUniversalLinksInIos(cordovaContext, pluginPreferences);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Activate Deep Links for Android application.
|
|
59
|
+
*
|
|
60
|
+
* @param {Object} cordovaContext - cordova context object
|
|
61
|
+
* @param {Object} pluginPreferences - plugin preferences from the config.xml file. Basically, content from <universal-links> tag.
|
|
62
|
+
*/
|
|
63
|
+
function activateUniversalLinksInAndroid(cordovaContext, pluginPreferences) {
|
|
64
|
+
// inject preferenes into AndroidManifest.xml
|
|
65
|
+
androidManifestWriter.writePreferences(cordovaContext, pluginPreferences);
|
|
66
|
+
|
|
67
|
+
// generate html file with the <link> tags that you should inject on the website.
|
|
68
|
+
androidWebHook.generate(cordovaContext, pluginPreferences);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Activate Universal Links for iOS application.
|
|
73
|
+
*
|
|
74
|
+
* @param {Object} cordovaContext - cordova context object
|
|
75
|
+
* @param {Object} pluginPreferences - plugin preferences from the config.xml file. Basically, content from <universal-links> tag.
|
|
76
|
+
*/
|
|
77
|
+
function activateUniversalLinksInIos(cordovaContext, pluginPreferences) {
|
|
78
|
+
// modify xcode project preferences
|
|
79
|
+
iosProjectPreferences.enableAssociativeDomainsCapability(cordovaContext);
|
|
80
|
+
|
|
81
|
+
// generate entitlements file
|
|
82
|
+
iosProjectEntitlements.generateAssociatedDomainsEntitlements(cordovaContext, pluginPreferences);
|
|
83
|
+
|
|
84
|
+
// generate apple-site-association-file
|
|
85
|
+
iosAppSiteAssociationFile.generate(cordovaContext, pluginPreferences);
|
|
86
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Hook is executed when plugin is added to the project.
|
|
3
|
+
It will check all necessary module dependencies and install the missing ones locally.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
var path = require('path');
|
|
7
|
+
var fs = require('fs');
|
|
8
|
+
var spawnSync = require('child_process').spawnSync;
|
|
9
|
+
var pluginNpmDependencies = require('../package.json').dependencies;
|
|
10
|
+
var INSTALLATION_FLAG_FILE_NAME = '.npmInstalled';
|
|
11
|
+
|
|
12
|
+
// region mark that we installed npm packages
|
|
13
|
+
/**
|
|
14
|
+
* Check if we already executed this hook.
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} ctx - cordova context
|
|
17
|
+
* @return {Boolean} true if already executed; otherwise - false
|
|
18
|
+
*/
|
|
19
|
+
function isInstallationAlreadyPerformed(ctx) {
|
|
20
|
+
var pathToInstallFlag = path.join(ctx.opts.projectRoot, 'plugins', ctx.opts.plugin.id, INSTALLATION_FLAG_FILE_NAME);
|
|
21
|
+
try {
|
|
22
|
+
fs.accessSync(pathToInstallFlag, fs.F_OK);
|
|
23
|
+
return true;
|
|
24
|
+
} catch (err) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Create empty file - indicator, that we tried to install dependency modules after installation.
|
|
31
|
+
* We have to do that, or this hook is gonna be called on any plugin installation.
|
|
32
|
+
*/
|
|
33
|
+
function createPluginInstalledFlag(ctx) {
|
|
34
|
+
var pathToInstallFlag = path.join(ctx.opts.projectRoot, 'plugins', ctx.opts.plugin.id, INSTALLATION_FLAG_FILE_NAME);
|
|
35
|
+
|
|
36
|
+
fs.closeSync(fs.openSync(pathToInstallFlag, 'w'));
|
|
37
|
+
}
|
|
38
|
+
// endregion
|
|
39
|
+
|
|
40
|
+
module.exports = function(ctx) {
|
|
41
|
+
if (isInstallationAlreadyPerformed(ctx)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
console.log('Installing dependency packages: ');
|
|
46
|
+
console.log(JSON.stringify(pluginNpmDependencies, null, 2));
|
|
47
|
+
|
|
48
|
+
var npm = (process.platform === "win32" ? "npm.cmd" : "npm");
|
|
49
|
+
var result = spawnSync(npm, ['install', '--production'], { cwd: './plugins/' + ctx.opts.plugin.id });
|
|
50
|
+
if (result.error) {
|
|
51
|
+
throw result.error;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
createPluginInstalledFlag(ctx);
|
|
55
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Hook executed before the 'prepare' stage. Only for iOS project.
|
|
3
|
+
It will check if project name has changed. If so - it will change the name of the .entitlements file to remove that file duplicates.
|
|
4
|
+
If file name has no changed - hook will do nothing.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
var path = require('path');
|
|
8
|
+
var fs = require('fs');
|
|
9
|
+
var ConfigXmlHelper = require('./lib/configXmlHelper.js');
|
|
10
|
+
|
|
11
|
+
module.exports = function(ctx) {
|
|
12
|
+
run(ctx);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Run the hook logic.
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} ctx - cordova context object
|
|
19
|
+
*/
|
|
20
|
+
function run(ctx) {
|
|
21
|
+
var projectRoot = ctx.opts.projectRoot;
|
|
22
|
+
var iosProjectFilePath = path.join(projectRoot, 'platforms', 'ios');
|
|
23
|
+
var configXmlHelper = new ConfigXmlHelper(ctx);
|
|
24
|
+
var newProjectName = configXmlHelper.getProjectName();
|
|
25
|
+
|
|
26
|
+
var oldProjectName = getOldProjectName(iosProjectFilePath);
|
|
27
|
+
|
|
28
|
+
// if name has not changed - do nothing
|
|
29
|
+
if (oldProjectName.length && oldProjectName === newProjectName) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log('Project name has changed. Renaming .entitlements file.');
|
|
34
|
+
|
|
35
|
+
// if it does - rename it
|
|
36
|
+
var oldEntitlementsFilePath = path.join(iosProjectFilePath, oldProjectName, 'Resources', oldProjectName + '.entitlements');
|
|
37
|
+
var newEntitlementsFilePath = path.join(iosProjectFilePath, oldProjectName, 'Resources', newProjectName + '.entitlements');
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
fs.renameSync(oldEntitlementsFilePath, newEntitlementsFilePath);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
console.warn('Failed to rename .entitlements file.');
|
|
43
|
+
console.warn(err);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// region Private API
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Get old name of the project.
|
|
51
|
+
* Name is detected by the name of the .xcodeproj file.
|
|
52
|
+
*
|
|
53
|
+
* @param {String} projectDir absolute path to ios project directory
|
|
54
|
+
* @return {String} old project name
|
|
55
|
+
*/
|
|
56
|
+
function getOldProjectName(projectDir) {
|
|
57
|
+
var files = [];
|
|
58
|
+
try {
|
|
59
|
+
files = fs.readdirSync(projectDir);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
return '';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
var projectFile = '';
|
|
65
|
+
files.forEach(function(fileName) {
|
|
66
|
+
if (path.extname(fileName) === '.xcodeproj') {
|
|
67
|
+
projectFile = path.basename(fileName, '.xcodeproj');
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return projectFile;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// endregion
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Class injects plugin preferences into AndroidManifest.xml file.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
var path = require('path');
|
|
6
|
+
var xmlHelper = require('../xmlHelper.js');
|
|
7
|
+
var fs = require('fs');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
writePreferences: writePreferences
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// region Public API
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Inject preferences into AndroidManifest.xml file.
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} cordovaContext - cordova context object
|
|
19
|
+
* @param {Object} pluginPreferences - plugin preferences as JSON object; already parsed
|
|
20
|
+
*/
|
|
21
|
+
function writePreferences(cordovaContext, pluginPreferences) {
|
|
22
|
+
var pathToManifest = path.join(cordovaContext.opts.projectRoot, 'platforms', 'android', 'AndroidManifest.xml');
|
|
23
|
+
if (!fs.existsSync(pathToManifest)) {
|
|
24
|
+
pathToManifest = path.join(cordovaContext.opts.projectRoot, 'platforms', 'android', 'app', 'src', 'main', 'AndroidManifest.xml');
|
|
25
|
+
}
|
|
26
|
+
var manifestSource = xmlHelper.readXmlAsJson(pathToManifest);
|
|
27
|
+
var cleanManifest;
|
|
28
|
+
var updatedManifest;
|
|
29
|
+
|
|
30
|
+
// remove old intent-filters
|
|
31
|
+
cleanManifest = removeOldOptions(manifestSource);
|
|
32
|
+
|
|
33
|
+
// inject intent-filters based on plugin preferences
|
|
34
|
+
updatedManifest = injectOptions(cleanManifest, pluginPreferences);
|
|
35
|
+
|
|
36
|
+
// save new version of the AndroidManifest
|
|
37
|
+
xmlHelper.writeJsonAsXml(updatedManifest, pathToManifest);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// endregion
|
|
41
|
+
|
|
42
|
+
// region Manifest cleanup methods
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Remove old intent-filters from the manifest file.
|
|
46
|
+
*
|
|
47
|
+
* @param {Object} manifestData - manifest content as JSON object
|
|
48
|
+
* @return {Object} manifest data without old intent-filters
|
|
49
|
+
*/
|
|
50
|
+
function removeOldOptions(manifestData) {
|
|
51
|
+
var cleanManifest = manifestData;
|
|
52
|
+
var activities = manifestData['manifest']['application'][0]['activity'];
|
|
53
|
+
|
|
54
|
+
activities.forEach(removeIntentFiltersFromActivity);
|
|
55
|
+
cleanManifest['manifest']['application'][0]['activity'] = activities;
|
|
56
|
+
|
|
57
|
+
return cleanManifest;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Remove old intent filters from the given activity.
|
|
62
|
+
*
|
|
63
|
+
* @param {Object} activity - activity, from which we need to remove intent-filters.
|
|
64
|
+
* Changes applied to the passed object.
|
|
65
|
+
*/
|
|
66
|
+
function removeIntentFiltersFromActivity(activity) {
|
|
67
|
+
var oldIntentFilters = activity['intent-filter'];
|
|
68
|
+
var newIntentFilters = [];
|
|
69
|
+
|
|
70
|
+
if (oldIntentFilters == null || oldIntentFilters.length == 0) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
oldIntentFilters.forEach(function(intentFilter) {
|
|
75
|
+
if (!isIntentFilterForUniversalLinks(intentFilter)) {
|
|
76
|
+
newIntentFilters.push(intentFilter);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
activity['intent-filter'] = newIntentFilters;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check if given intent-filter is for Universal Links.
|
|
85
|
+
*
|
|
86
|
+
* @param {Object} intentFilter - intent-filter to check
|
|
87
|
+
* @return {Boolean} true - if intent-filter for Universal Links; otherwise - false;
|
|
88
|
+
*/
|
|
89
|
+
function isIntentFilterForUniversalLinks(intentFilter) {
|
|
90
|
+
var actions = intentFilter['action'];
|
|
91
|
+
var categories = intentFilter['category'];
|
|
92
|
+
var data = intentFilter['data'];
|
|
93
|
+
|
|
94
|
+
return isActionForUniversalLinks(actions) &&
|
|
95
|
+
isCategoriesForUniversalLinks(categories) &&
|
|
96
|
+
isDataTagForUniversalLinks(data);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Check if actions from the intent-filter corresponds to actions for Universal Links.
|
|
101
|
+
*
|
|
102
|
+
* @param {Array} actions - list of actions in the intent-filter
|
|
103
|
+
* @return {Boolean} true - if action for Universal Links; otherwise - false
|
|
104
|
+
*/
|
|
105
|
+
function isActionForUniversalLinks(actions) {
|
|
106
|
+
// there can be only 1 action
|
|
107
|
+
if (actions == null || actions.length != 1) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
var action = actions[0]['$']['android:name'];
|
|
112
|
+
|
|
113
|
+
return ('android.intent.action.VIEW' === action);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Check if categories in the intent-filter corresponds to categories for Universal Links.
|
|
118
|
+
*
|
|
119
|
+
* @param {Array} categories - list of categories in the intent-filter
|
|
120
|
+
* @return {Boolean} true - if action for Universal Links; otherwise - false
|
|
121
|
+
*/
|
|
122
|
+
function isCategoriesForUniversalLinks(categories) {
|
|
123
|
+
// there can be only 2 categories
|
|
124
|
+
if (categories == null || categories.length != 2) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
var isBrowsable = false;
|
|
129
|
+
var isDefault = false;
|
|
130
|
+
|
|
131
|
+
// check intent categories
|
|
132
|
+
categories.forEach(function(category) {
|
|
133
|
+
var categoryName = category['$']['android:name'];
|
|
134
|
+
if (!isBrowsable) {
|
|
135
|
+
isBrowsable = 'android.intent.category.BROWSABLE' === categoryName;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!isDefault) {
|
|
139
|
+
isDefault = 'android.intent.category.DEFAULT' === categoryName;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return isDefault && isBrowsable;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if data tag from intent-filter corresponds to data for Universal Links.
|
|
148
|
+
*
|
|
149
|
+
* @param {Array} data - list of data tags in the intent-filter
|
|
150
|
+
* @return {Boolean} true - if data tag for Universal Links; otherwise - false
|
|
151
|
+
*/
|
|
152
|
+
function isDataTagForUniversalLinks(data) {
|
|
153
|
+
// can have only 1 data tag in the intent-filter
|
|
154
|
+
if (data == null || data.length != 1) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
var dataHost = data[0]['$']['android:host'];
|
|
159
|
+
var dataScheme = data[0]['$']['android:scheme'];
|
|
160
|
+
var hostIsSet = dataHost != null && dataHost.length > 0;
|
|
161
|
+
var schemeIsSet = dataScheme != null && dataScheme.length > 0;
|
|
162
|
+
|
|
163
|
+
return hostIsSet && schemeIsSet;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// endregion
|
|
167
|
+
|
|
168
|
+
// region Methods to inject preferences into AndroidManifest.xml file
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Inject options into manifest file.
|
|
172
|
+
*
|
|
173
|
+
* @param {Object} manifestData - manifest content where preferences should be injected
|
|
174
|
+
* @param {Object} pluginPreferences - plugin preferences from config.xml; already parsed
|
|
175
|
+
* @return {Object} updated manifest data with corresponding intent-filters
|
|
176
|
+
*/
|
|
177
|
+
function injectOptions(manifestData, pluginPreferences) {
|
|
178
|
+
var changedManifest = manifestData;
|
|
179
|
+
var activitiesList = changedManifest['manifest']['application'][0]['activity'];
|
|
180
|
+
var launchActivityIndex = getMainLaunchActivityIndex(activitiesList);
|
|
181
|
+
var ulIntentFilters = [];
|
|
182
|
+
var launchActivity;
|
|
183
|
+
|
|
184
|
+
if (launchActivityIndex < 0) {
|
|
185
|
+
console.warn('Could not find launch activity in the AndroidManifest file. Can\'t inject Universal Links preferences.');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// get launch activity
|
|
190
|
+
launchActivity = activitiesList[launchActivityIndex];
|
|
191
|
+
|
|
192
|
+
// generate intent-filters
|
|
193
|
+
pluginPreferences.hosts.forEach(function(host) {
|
|
194
|
+
host.paths.forEach(function(hostPath) {
|
|
195
|
+
ulIntentFilters.push(createIntentFilter(host.name, host.scheme, hostPath));
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// add Universal Links intent-filters to the launch activity
|
|
200
|
+
launchActivity['intent-filter'] = launchActivity['intent-filter'].concat(ulIntentFilters);
|
|
201
|
+
|
|
202
|
+
return changedManifest;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Find index of the applications launcher activity.
|
|
207
|
+
*
|
|
208
|
+
* @param {Array} activities - list of all activities in the app
|
|
209
|
+
* @return {Integer} index of the launch activity; -1 - if none was found
|
|
210
|
+
*/
|
|
211
|
+
function getMainLaunchActivityIndex(activities) {
|
|
212
|
+
var launchActivityIndex = -1;
|
|
213
|
+
activities.some(function(activity, index) {
|
|
214
|
+
if (isLaunchActivity(activity)) {
|
|
215
|
+
launchActivityIndex = index;
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return false;
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return launchActivityIndex;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Check if the given actvity is a launch activity.
|
|
227
|
+
*
|
|
228
|
+
* @param {Object} activity - activity to check
|
|
229
|
+
* @return {Boolean} true - if this is a launch activity; otherwise - false
|
|
230
|
+
*/
|
|
231
|
+
function isLaunchActivity(activity) {
|
|
232
|
+
var intentFilters = activity['intent-filter'];
|
|
233
|
+
var isLauncher = false;
|
|
234
|
+
|
|
235
|
+
if (intentFilters == null || intentFilters.length == 0) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
isLauncher = intentFilters.some(function(intentFilter) {
|
|
240
|
+
var action = intentFilter['action'];
|
|
241
|
+
var category = intentFilter['category'];
|
|
242
|
+
|
|
243
|
+
if (action == null || action.length != 1 || category == null || category.length != 1) {
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
var isMainAction = ('android.intent.action.MAIN' === action[0]['$']['android:name']);
|
|
248
|
+
var isLauncherCategory = ('android.intent.category.LAUNCHER' === category[0]['$']['android:name']);
|
|
249
|
+
|
|
250
|
+
return isMainAction && isLauncherCategory;
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
return isLauncher;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Create JSON object that represent intent-filter for universal link.
|
|
258
|
+
*
|
|
259
|
+
* @param {String} host - host name
|
|
260
|
+
* @param {String} scheme - host scheme
|
|
261
|
+
* @param {String} pathName - host path
|
|
262
|
+
* @return {Object} intent-filter as a JSON object
|
|
263
|
+
*/
|
|
264
|
+
function createIntentFilter(host, scheme, pathName) {
|
|
265
|
+
var intentFilter = {
|
|
266
|
+
'$': {
|
|
267
|
+
'android:autoVerify': 'true'
|
|
268
|
+
},
|
|
269
|
+
'action': [{
|
|
270
|
+
'$': {
|
|
271
|
+
'android:name': 'android.intent.action.VIEW'
|
|
272
|
+
}
|
|
273
|
+
}],
|
|
274
|
+
'category': [{
|
|
275
|
+
'$': {
|
|
276
|
+
'android:name': 'android.intent.category.DEFAULT'
|
|
277
|
+
}
|
|
278
|
+
}, {
|
|
279
|
+
'$': {
|
|
280
|
+
'android:name': 'android.intent.category.BROWSABLE'
|
|
281
|
+
}
|
|
282
|
+
}],
|
|
283
|
+
'data': [{
|
|
284
|
+
'$': {
|
|
285
|
+
'android:host': host,
|
|
286
|
+
'android:scheme': scheme
|
|
287
|
+
}
|
|
288
|
+
}]
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
injectPathComponentIntoIntentFilter(intentFilter, pathName);
|
|
292
|
+
|
|
293
|
+
return intentFilter;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Inject host path into provided intent-filter.
|
|
298
|
+
*
|
|
299
|
+
* @param {Object} intentFilter - intent-filter object where path component should be injected
|
|
300
|
+
* @param {String} pathName - host path to inject
|
|
301
|
+
*/
|
|
302
|
+
function injectPathComponentIntoIntentFilter(intentFilter, pathName) {
|
|
303
|
+
if (pathName == null || pathName === '*') {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
var attrKey = 'android:path';
|
|
308
|
+
if (pathName.indexOf('*') >= 0) {
|
|
309
|
+
attrKey = 'android:pathPattern';
|
|
310
|
+
pathName = pathName.replace(/\*/g, '.*');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (pathName.indexOf('/') != 0) {
|
|
314
|
+
pathName = '/' + pathName;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
intentFilter['data'][0]['$'][attrKey] = pathName;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// endregion
|