@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.
Files changed (51) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/LICENSE +22 -0
  3. package/README.md +908 -0
  4. package/docs/images/app-associated-domains.jpg +0 -0
  5. package/docs/images/app-id-team-prefix.jpg +0 -0
  6. package/docs/images/app-id.jpg +0 -0
  7. package/docs/images/branch-io-link-domain.jpg +0 -0
  8. package/docs/images/branch-io.jpg +0 -0
  9. package/docs/images/developer-console.jpg +0 -0
  10. package/hooks/afterPrepareHook.js +86 -0
  11. package/hooks/beforePluginInstallHook.js +55 -0
  12. package/hooks/iosBeforePrepareHook.js +74 -0
  13. package/hooks/lib/android/manifestWriter.js +320 -0
  14. package/hooks/lib/android/webSiteHook.js +162 -0
  15. package/hooks/lib/configXmlHelper.js +117 -0
  16. package/hooks/lib/configXmlParser.js +145 -0
  17. package/hooks/lib/ios/appleAppSiteAssociationFile.js +173 -0
  18. package/hooks/lib/ios/projectEntitlements.js +174 -0
  19. package/hooks/lib/ios/xcodePreferences.js +243 -0
  20. package/hooks/lib/xmlHelper.js +57 -0
  21. package/package.json +58 -0
  22. package/plugin.xml +106 -0
  23. package/src/android/com/nordnetab/cordova/ul/UniversalLinksPlugin.java +221 -0
  24. package/src/android/com/nordnetab/cordova/ul/js/JSAction.java +19 -0
  25. package/src/android/com/nordnetab/cordova/ul/model/JSMessage.java +193 -0
  26. package/src/android/com/nordnetab/cordova/ul/model/ULHost.java +85 -0
  27. package/src/android/com/nordnetab/cordova/ul/model/ULPath.java +43 -0
  28. package/src/android/com/nordnetab/cordova/ul/parser/ULConfigXmlParser.java +156 -0
  29. package/src/android/com/nordnetab/cordova/ul/parser/XmlTags.java +49 -0
  30. package/src/ios/AppDelegate+CULPlugin.h +17 -0
  31. package/src/ios/AppDelegate+CULPlugin.m +32 -0
  32. package/src/ios/CULPlugin.h +38 -0
  33. package/src/ios/CULPlugin.m +176 -0
  34. package/src/ios/JS/CDVInvokedUrlCommand+CULPlugin.h +21 -0
  35. package/src/ios/JS/CDVInvokedUrlCommand+CULPlugin.m +19 -0
  36. package/src/ios/JS/CDVPluginResult+CULPlugin.h +29 -0
  37. package/src/ios/JS/CDVPluginResult+CULPlugin.m +157 -0
  38. package/src/ios/Model/CULHost.h +63 -0
  39. package/src/ios/Model/CULHost.m +50 -0
  40. package/src/ios/Model/CULPath.h +36 -0
  41. package/src/ios/Model/CULPath.m +21 -0
  42. package/src/ios/Parser/JSON/CULConfigJsonParser.h +22 -0
  43. package/src/ios/Parser/JSON/CULConfigJsonParser.m +60 -0
  44. package/src/ios/Parser/XML/CULConfigXmlParser.h +22 -0
  45. package/src/ios/Parser/XML/CULConfigXmlParser.m +123 -0
  46. package/src/ios/Parser/XML/CULXmlTags.h +54 -0
  47. package/src/ios/Parser/XML/CULXmlTags.m +22 -0
  48. package/src/ios/Utils/NSBundle+CULPlugin.h +22 -0
  49. package/src/ios/Utils/NSBundle+CULPlugin.m +15 -0
  50. package/ul_web_hooks/android_web_hook_tpl.html +23 -0
  51. package/www/universal_links.js +56 -0
@@ -0,0 +1,162 @@
1
+ /*
2
+ Class creates android_web_hook.html file in your Cordova project root folder.
3
+ File holds <link /> tags, which are generated based on data, specified in config.xml.
4
+ You need to include those tags on your website to link web pages to the content inside your application.
5
+
6
+ More documentation on that can be found here:
7
+ https://developer.android.com/training/app-indexing/enabling-app-indexing.html
8
+ */
9
+
10
+ var fs = require('fs');
11
+ var path = require('path');
12
+ var mkpath = require('mkpath');
13
+ var ConfigXmlHelper = require('../configXmlHelper.js');
14
+ var WEB_HOOK_FILE_PATH = path.join('ul_web_hooks', 'android', 'android_web_hook.html');
15
+ var WEB_HOOK_TPL_FILE_PATH = path.join('plugins', 'cordova-plugin-universal-links', 'ul_web_hooks', 'android_web_hook_tpl.html');
16
+ var LINK_PLACEHOLDER = '[__LINKS__]';
17
+ var LINK_TEMPLATE = '<link rel="alternate" href="android-app://<package_name>/<scheme>/<host><path>" />';
18
+
19
+ module.exports = {
20
+ generate: generateWebHook
21
+ };
22
+
23
+ // region Public API
24
+
25
+ /**
26
+ * Generate website hook for android application.
27
+ *
28
+ * @param {Object} cordovaContext - cordova context object
29
+ * @param {Object} pluginPreferences - plugin preferences from config.xml file; already parsed
30
+ */
31
+ function generateWebHook(cordovaContext, pluginPreferences) {
32
+ var projectRoot = cordovaContext.opts.projectRoot;
33
+ var configXmlHelper = new ConfigXmlHelper(cordovaContext);
34
+ var packageName = configXmlHelper.getPackageName('android');
35
+ var template = readTemplate(projectRoot);
36
+
37
+ // if template was not found - exit
38
+ if (template == null || template.length == 0) {
39
+ return;
40
+ }
41
+
42
+ // generate hook content
43
+ var linksToInsert = generateLinksSet(projectRoot, packageName, pluginPreferences);
44
+ var hookContent = template.replace(LINK_PLACEHOLDER, linksToInsert);
45
+
46
+ // save hook
47
+ saveWebHook(projectRoot, hookContent);
48
+ }
49
+
50
+ // endregion
51
+
52
+ // region Public API
53
+
54
+ /**
55
+ * Read hook teplate from plugin directory.
56
+ *
57
+ * @param {String} projectRoot - absolute path to cordova's project root
58
+ * @return {String} data from the template file
59
+ */
60
+ function readTemplate(projectRoot) {
61
+ var filePath = path.join(projectRoot, WEB_HOOK_TPL_FILE_PATH);
62
+ var tplData = null;
63
+
64
+ try {
65
+ tplData = fs.readFileSync(filePath, 'utf8');
66
+ } catch (err) {
67
+ console.warn('Template file for android web hook is not found!');
68
+ console.warn(err);
69
+ }
70
+
71
+ return tplData;
72
+ }
73
+
74
+ /**
75
+ * Generate list of <link /> tags based on plugin preferences.
76
+ *
77
+ * @param {String} projectRoot - absolute path to cordova's project root
78
+ * @param {String} packageName - android application package name
79
+ * @param {Object} pluginPreferences - plugin preferences, defined in config.xml; already parsed
80
+ * @return {String} list of <link /> tags
81
+ */
82
+ function generateLinksSet(projectRoot, packageName, pluginPreferences) {
83
+ var linkTpl = LINK_TEMPLATE.replace('<package_name>', packageName);
84
+ var content = '';
85
+
86
+ pluginPreferences.hosts.forEach(function(host) {
87
+ host.paths.forEach(function(hostPath) {
88
+ content += generateLinkTag(linkTpl, host.scheme, host.name, hostPath) + '\n';
89
+ });
90
+ });
91
+
92
+ return content;
93
+ }
94
+
95
+ /**
96
+ * Generate <link /> tag.
97
+ *
98
+ * @param {String} linkTpl - template to use for tag generation
99
+ * @param {String} scheme - host scheme
100
+ * @param {String} host - host name
101
+ * @param {String} path - host path
102
+ * @return {String} <link /> tag
103
+ */
104
+ function generateLinkTag(linkTpl, scheme, host, path) {
105
+ linkTpl = linkTpl.replace('<scheme>', scheme).replace('<host>', host);
106
+ if (path == null || path === '*') {
107
+ return linkTpl.replace('<path>', '');
108
+ }
109
+
110
+ // for android we need to replace * with .* for pattern matching
111
+ if (path.indexOf('*') >= 0) {
112
+ path = path.replace(/\*/g, '.*');
113
+ }
114
+
115
+ // path should start with /
116
+ if (path.indexOf('/') != 0) {
117
+ path = '/' + path;
118
+ }
119
+
120
+ return linkTpl.replace('<path>', path);
121
+ }
122
+
123
+ /**
124
+ * Save data to website hook file.
125
+ *
126
+ * @param {String} projectRoot - absolute path to project root
127
+ * @param {String} hookContent - data to save
128
+ * @return {boolean} true - if data was saved; otherwise - false;
129
+ */
130
+ function saveWebHook(projectRoot, hookContent) {
131
+ var filePath = path.join(projectRoot, WEB_HOOK_FILE_PATH);
132
+ var isSaved = true;
133
+
134
+ // ensure directory exists
135
+ createDirectoryIfNeeded(path.dirname(filePath));
136
+
137
+ // write data to file
138
+ try {
139
+ fs.writeFileSync(filePath, hookContent, 'utf8');
140
+ } catch (err) {
141
+ console.warn('Failed to create android web hook!');
142
+ console.warn(err);
143
+ isSaved = false;
144
+ }
145
+
146
+ return isSaved;
147
+ }
148
+
149
+ /**
150
+ * Create directory if it doesn't exist yet.
151
+ *
152
+ * @param {String} dir - absolute path to directory
153
+ */
154
+ function createDirectoryIfNeeded(dir) {
155
+ try {
156
+ mkpath.sync(dir);
157
+ } catch (err) {
158
+ console.log(err);
159
+ }
160
+ }
161
+
162
+ // endregion
@@ -0,0 +1,117 @@
1
+ /*
2
+ Helper class to read data from config.xml file.
3
+ */
4
+ var path = require('path');
5
+ var xmlHelper = require('./xmlHelper.js');
6
+ var ANDROID = 'android';
7
+ var IOS = 'ios';
8
+ var CONFIG_FILE_NAME = 'config.xml';
9
+ var context;
10
+ var projectRoot;
11
+
12
+ module.exports = ConfigXmlHelper;
13
+
14
+ // region public API
15
+
16
+ /**
17
+ * Constructor.
18
+ *
19
+ * @param {Object} cordovaContext - cordova context object
20
+ */
21
+ function ConfigXmlHelper(cordovaContext) {
22
+ context = cordovaContext;
23
+ projectRoot = context.opts.projectRoot;
24
+ }
25
+
26
+ /**
27
+ * Read config.xml data as JSON object.
28
+ *
29
+ * @return {Object} JSON object with data from config.xml
30
+ */
31
+ ConfigXmlHelper.prototype.read = function() {
32
+ var filePath = getConfigXmlFilePath();
33
+
34
+ return xmlHelper.readXmlAsJson(filePath);
35
+ }
36
+
37
+ /**
38
+ * Get package name for the application. Depends on the platform.
39
+ *
40
+ * @param {String} platform - 'ios' or 'android'; for what platform we need a package name
41
+ * @return {String} package/bundle name
42
+ */
43
+ ConfigXmlHelper.prototype.getPackageName = function(platform) {
44
+ var configFilePath = getConfigXmlFilePath();
45
+ var config = getCordovaConfigParser(configFilePath);
46
+ var packageName;
47
+
48
+ switch (platform) {
49
+ case ANDROID:
50
+ {
51
+ packageName = config.android_packageName();
52
+ break;
53
+ }
54
+ case IOS:
55
+ {
56
+ packageName = config.ios_CFBundleIdentifier();
57
+ break;
58
+ }
59
+ }
60
+ if (packageName === undefined || packageName.length == 0) {
61
+ packageName = config.packageName();
62
+ }
63
+
64
+ return packageName;
65
+ }
66
+
67
+ /**
68
+ * Get name of the current project.
69
+ *
70
+ * @return {String} name of the project
71
+ */
72
+ ConfigXmlHelper.prototype.getProjectName = function() {
73
+ return getProjectName();
74
+ }
75
+
76
+ // endregion
77
+
78
+ // region Private API
79
+
80
+ /**
81
+ * Get config parser from cordova library.
82
+ *
83
+ * @param {String} configFilePath absolute path to the config.xml file
84
+ * @return {Object}
85
+ */
86
+ function getCordovaConfigParser(configFilePath) {
87
+ var ConfigParser;
88
+
89
+ // If we are running Cordova 5.4 or abova - use parser from cordova-common.
90
+ // Otherwise - from cordova-lib.
91
+ try {
92
+ ConfigParser = context.requireCordovaModule('cordova-common/src/ConfigParser/ConfigParser');
93
+ } catch (e) {
94
+ ConfigParser = context.requireCordovaModule('cordova-lib/src/configparser/ConfigParser')
95
+ }
96
+
97
+ return new ConfigParser(configFilePath);
98
+ }
99
+
100
+ /**
101
+ * Get absolute path to the config.xml.
102
+ */
103
+ function getConfigXmlFilePath() {
104
+ return path.join(projectRoot, CONFIG_FILE_NAME);
105
+ }
106
+
107
+ /**
108
+ * Get project name from config.xml
109
+ */
110
+ function getProjectName() {
111
+ var configFilePath = getConfigXmlFilePath();
112
+ var config = getCordovaConfigParser(configFilePath);
113
+
114
+ return config.name();
115
+ }
116
+
117
+ // endregion
@@ -0,0 +1,145 @@
1
+ /*
2
+ Parser for config.xml file. Read plugin-specific preferences (from <universal-links> tag) as JSON object.
3
+ */
4
+ var path = require('path');
5
+ var ConfigXmlHelper = require('./configXmlHelper.js');
6
+ var DEFAULT_SCHEME = 'http';
7
+
8
+ module.exports = {
9
+ readPreferences: readPreferences
10
+ };
11
+
12
+ // region Public API
13
+
14
+ /**
15
+ * Read plugin preferences from the config.xml file.
16
+ *
17
+ * @param {Object} cordovaContext - cordova context object
18
+ * @return {Array} list of host objects
19
+ */
20
+ function readPreferences(cordovaContext) {
21
+ // read data from projects root config.xml file
22
+ var configXml = new ConfigXmlHelper(cordovaContext).read();
23
+ if (configXml == null) {
24
+ console.warn('config.xml not found! Please, check that it exist\'s in your project\'s root directory.');
25
+ return null;
26
+ }
27
+
28
+ // look for data from the <universal-links> tag
29
+ var ulXmlPreferences = configXml.widget['universal-links'];
30
+ if (ulXmlPreferences == null || ulXmlPreferences.length == 0) {
31
+ console.warn('<universal-links> tag is not set in the config.xml. Universal Links plugin is not going to work.');
32
+ return null;
33
+ }
34
+
35
+ var xmlPreferences = ulXmlPreferences[0];
36
+
37
+ // read hosts
38
+ var hosts = constructHostsList(xmlPreferences);
39
+
40
+ // read ios team ID
41
+ var iosTeamId = getTeamIdPreference(xmlPreferences);
42
+
43
+ return {
44
+ 'hosts': hosts,
45
+ 'iosTeamId': iosTeamId
46
+ };
47
+ }
48
+
49
+ // endregion
50
+
51
+ // region Private API
52
+
53
+ function getTeamIdPreference(xmlPreferences) {
54
+ if (xmlPreferences.hasOwnProperty('ios-team-id')) {
55
+ return xmlPreferences['ios-team-id'][0]['$']['value'];
56
+ }
57
+
58
+ return null;
59
+ }
60
+
61
+ /**
62
+ * Construct list of host objects, defined in xml file.
63
+ *
64
+ * @param {Object} xmlPreferences - plugin preferences from config.xml as JSON object
65
+ * @return {Array} array of JSON objects, where each entry defines host data from config.xml.
66
+ */
67
+ function constructHostsList(xmlPreferences) {
68
+ var hostsList = [];
69
+
70
+ // look for defined hosts
71
+ var xmlHostList = xmlPreferences['host'];
72
+ if (xmlHostList == null || xmlHostList.length == 0) {
73
+ return [];
74
+ }
75
+
76
+ xmlHostList.forEach(function(xmlElement) {
77
+ var host = constructHostEntry(xmlElement);
78
+ if (host) {
79
+ hostsList.push(host);
80
+ }
81
+ });
82
+
83
+ return hostsList;
84
+ }
85
+
86
+ /**
87
+ * Construct host object from xml data.
88
+ *
89
+ * @param {Object} xmlElement - xml data to process.
90
+ * @return {Object} host entry as JSON object
91
+ */
92
+ function constructHostEntry(xmlElement) {
93
+ var host = {
94
+ scheme: DEFAULT_SCHEME,
95
+ name: '',
96
+ paths: []
97
+ };
98
+ var hostProperties = xmlElement['$'];
99
+
100
+ if (hostProperties == null || hostProperties.length == 0) {
101
+ return null;
102
+ }
103
+
104
+ // read host name
105
+ host.name = hostProperties.name;
106
+
107
+ // read scheme if defined
108
+ if (hostProperties['scheme'] != null) {
109
+ host.scheme = hostProperties.scheme;
110
+ }
111
+
112
+ // construct paths list, defined for the given host
113
+ host.paths = constructPaths(xmlElement);
114
+
115
+ return host;
116
+ }
117
+
118
+ /**
119
+ * Construct list of path objects from the xml data.
120
+ *
121
+ * @param {Object} xmlElement - xml data to process
122
+ * @return {Array} list of path entries, each on is a JSON object
123
+ */
124
+ function constructPaths(xmlElement) {
125
+ if (xmlElement['path'] == null) {
126
+ return ['*'];
127
+ }
128
+
129
+ var paths = [];
130
+ xmlElement.path.some(function(pathElement) {
131
+ var url = pathElement['$']['url'];
132
+
133
+ // Ignore explicit paths if '*' is defined
134
+ if (url === '*') {
135
+ paths = ['*'];
136
+ return true;
137
+ }
138
+
139
+ paths.push(url);
140
+ });
141
+
142
+ return paths;
143
+ }
144
+
145
+ // endregion
@@ -0,0 +1,173 @@
1
+ /*
2
+ Script generates apple-app-site-association files: one for each domain, defined in config.xml.
3
+ It is executed on 'after prepare' stage, usually when you execute 'cordova build'. Files are placed in 'ul_web_hooks/ios/' folder
4
+ of your projects root.
5
+
6
+ Files are created with the following name:
7
+ hostname#apple-app-site-association
8
+
9
+ Prefix 'hostname#' describes on which host this file should be placed. Don't forget to remove it before uploading file on your host.
10
+ Also, in the file you need to replace <YOUR_TEAM_ID_FROM_MEMBER_CENTER> with the real team id from the member center, if <ios-team-id> preference was not set in projects config.xml.
11
+
12
+ In order to activate support for Universal Links on iOS you need to sign them with the valid SSL certificate and place in the root of your domain.
13
+
14
+ Additional documentation regarding apple-app-site-association file can be found here:
15
+ - https://developer.apple.com/library/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html
16
+ - https://developer.apple.com/library/ios/documentation/Security/Reference/SharedWebCredentialsRef/index.html#//apple_ref/doc/uid/TP40014989
17
+ */
18
+
19
+
20
+ var path = require('path');
21
+ var mkpath = require('mkpath');
22
+ var fs = require('fs');
23
+ var rimraf = require('rimraf');
24
+ var ConfigXmlHelper = require('../configXmlHelper.js');
25
+ var IOS_TEAM_ID = '<YOUR_TEAM_ID_FROM_MEMBER_CENTER>';
26
+ var ASSOCIATION_FILE_NAME = 'apple-app-site-association';
27
+ var bundleId;
28
+ var context;
29
+
30
+ module.exports = {
31
+ generate: generate
32
+ };
33
+
34
+ // region Public API
35
+
36
+ /**
37
+ * Generate apple-app-site-association files.
38
+ *
39
+ * @param {Object} cordovaContext - cordova context object
40
+ * @param {Object} pluginPreferences - list of hosts from the config.xml; already parsed
41
+ */
42
+ function generate(cordovaContext, pluginPreferences) {
43
+ context = cordovaContext;
44
+ removeOldFiles();
45
+ createNewAssociationFiles(pluginPreferences);
46
+ }
47
+
48
+ // endregion
49
+
50
+ // region Content generation
51
+
52
+ /**
53
+ * Remove old files from ul_web_hooks/ios folder.
54
+ */
55
+ function removeOldFiles() {
56
+ rimraf.sync(getWebHookDirectory());
57
+ }
58
+
59
+ /**
60
+ * Generate new set of apple-app-site-association files.
61
+ *
62
+ * @param {Object} pluginPreferences - list of hosts from config.xml
63
+ */
64
+ function createNewAssociationFiles(pluginPreferences) {
65
+ var teamId = pluginPreferences.iosTeamId;
66
+ if (!teamId) {
67
+ teamId = IOS_TEAM_ID;
68
+ }
69
+
70
+ pluginPreferences.hosts.forEach(function(host) {
71
+ var content = generateFileContentForHost(host, teamId);
72
+ saveContentToFile(host.name, content);
73
+ });
74
+ }
75
+
76
+ /**
77
+ * Generate content of the apple-app-site-association file for the specific host.
78
+ *
79
+ * @param {Object} host - host information
80
+ * @return {Object} content of the file as JSON object
81
+ */
82
+ function generateFileContentForHost(host, teamId) {
83
+ var appID = teamId + '.' + getBundleId();
84
+ var paths = host.paths.slice();
85
+
86
+ // if paths are '*' - we should add '/' to it to support root domains.
87
+ // https://github.com/nordnet/cordova-universal-links-plugin/issues/46
88
+ if (paths.length == 1 && paths[0] === '*') {
89
+ paths.push('/');
90
+ }
91
+
92
+ return {
93
+ "applinks": {
94
+ "apps": [],
95
+ "details": [{
96
+ "appID": appID,
97
+ "paths": paths
98
+ }]
99
+ }
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Save data to the the apple-app-site-association file.
105
+ *
106
+ * @param {String} filePrefix - prefix for the generated file; usually - hostname
107
+ * @param {Object} content - file content as JSON object
108
+ */
109
+ function saveContentToFile(filePrefix, content) {
110
+ var dirPath = getWebHookDirectory();
111
+ var filePath = path.join(dirPath, filePrefix + '#' + ASSOCIATION_FILE_NAME);
112
+
113
+ // create all directories from file path
114
+ createDirectoriesIfNeeded(dirPath);
115
+
116
+ // write content to the file
117
+ try {
118
+ fs.writeFileSync(filePath, JSON.stringify(content, null, 2), 'utf8');
119
+ } catch (err) {
120
+ console.log(err);
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Create all directories from the given path.
126
+ *
127
+ * @param {String} dirPath - full path to directory
128
+ */
129
+ function createDirectoriesIfNeeded(dirPath) {
130
+ try {
131
+ mkpath.sync(dirPath);
132
+ } catch (err) {
133
+ console.log(err);
134
+ }
135
+ }
136
+
137
+ // endregion
138
+
139
+ // region Support methods
140
+
141
+ /**
142
+ * Path to the ios web hook directory.
143
+ *
144
+ * @return {String} path to web hook directory
145
+ */
146
+ function getWebHookDirectory() {
147
+ return path.join(getProjectRoot(), 'ul_web_hooks', 'ios');
148
+ }
149
+
150
+ /**
151
+ * Project root directory
152
+ *
153
+ * @return {String} absolute path to project root
154
+ */
155
+ function getProjectRoot() {
156
+ return context.opts.projectRoot;
157
+ }
158
+
159
+ /**
160
+ * Get bundle id from the config.xml file.
161
+ *
162
+ * @return {String} bundle id
163
+ */
164
+ function getBundleId() {
165
+ if (bundleId === undefined) {
166
+ var configXmlHelper = new ConfigXmlHelper(context);
167
+ bundleId = configXmlHelper.getPackageName('ios');
168
+ }
169
+
170
+ return bundleId;
171
+ }
172
+
173
+ // endregion