Last active
February 1, 2016 07:36
-
-
Save prabeengiri/f7ab6c569f0ab15e3e30 to your computer and use it in GitHub Desktop.
AdvancedNativeAppLauncher
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* | |
* @return object | |
* Javascript object with 'init' method to initialize | |
* NativeAppLauncher | |
*/ | |
var NativeAppLauncher = (function($) { | |
"use strict"; | |
var Settings = {}; | |
/** | |
* This object is acts as a Adapter pattern and created to achieve | |
* parameterized Strategy Pattern. | |
* This is implemented, so that AppLaunchStrategies can share same interface | |
* as they accepts different arguments to run. So strategies don't have to worry about the | |
* arguments as Strategy Parameters contains all the parameters required by all the strategies. | |
*/ | |
var AppLaunchStrategyParameters = { | |
getAppUri : function() { | |
if (!Settings.appUri) { | |
throw new Error('Settings does not have valid AppURI') | |
} | |
return Settings.appUri; | |
}, | |
getAppLauncherEl : function() { | |
if (!Settings.appLauncherElId) { | |
throw new Error('Settings does not have valid appLauncherElId'); | |
} | |
return $('#' + Settings.appLauncherElId); | |
}, | |
// This message is displayed if any browser does not support deep linking. | |
getNotSupportedMessage : function() { | |
if (!Settings.appLauncherElId) { | |
throw new Error('Settings does not have valid NotSupportedMessage'); | |
} | |
return Settings.notSupportedMessage; | |
}, | |
//TODO: Campaign can be created as separate decorator/proxy object | |
getCampaignValue: function() { | |
var queryString = Utils.getQueryString(); | |
return queryString[Settings.campaignKey]; | |
} | |
}; | |
/** | |
* @inherits AppLaunchStrategyParameters | |
* Extends Base App Launch Strategy Parameters which are required for | |
* Android App Launch Strategies. | |
*/ | |
var AndroidAppLaunchStrategyParameters = $.extend({ | |
getAppUri: function () { | |
return "intent://m/#Intent;scheme=" + Settings.getAppUri + ";package="+ Settings.androidAppId +";end"; | |
}, | |
getAppStoreURI: function () { | |
var campaignString = this.getCampaignValue() ? "&referrer=utm_source%3Dother%26utm_campaign%3D" + this.getCampaignValue() : ""; | |
return "https://play.google.com/store/apps/details?id=" + Settings.androidAppId + campaignString ; | |
} | |
}, AppLaunchStrategyParameters); | |
/** | |
* Extends Base App Launch Strategy Parameters which are required for | |
* Ios App Launch Strategies. | |
*/ | |
var IOSAppLaunchStrategyParameters = $.extend({ | |
getAppStoreURI: function () { | |
return this.getCampaignValue() ? | |
Utils.appendQueryParameter(Settings.iOsAppStore, 'ct', this.getCampaignValue()) : Settings.iOsAppStore; | |
}, | |
getUniversalLinkingUrl : function() { | |
return Settings.universalLinkUrl; | |
} | |
}, AppLaunchStrategyParameters); | |
/** | |
* Base AppLaunch Strategy. | |
* @param strategyParameters | |
* @constructor | |
*/ | |
var AppLaunchStrategy = function (strategyParameters) { | |
this.strategyParameters = strategyParameters; | |
this.init = function(){}; | |
}; | |
/** | |
* This method works with most of the old devices and browsers. | |
* This first tries to launch App using custom uri scheme(twitter://). If app is not installed, | |
* then after few milliseconds, it redirects to app store as browser does not provides api to | |
* detect if app is installed or not. | |
* | |
* @param strategyParameters | |
* @constructor | |
*/ | |
var DirectAppLaunchStrategy = function(strategyParameters) { | |
AppLaunchStrategy.call(this, strategyParameters); | |
this.init = function() { | |
window.location.href = strategyParameters.getAppUri(); | |
setTimeout(function() { | |
window.location.href = strategyParameters.getAppStoreURI(); | |
}, 500) | |
} | |
}; | |
/** | |
* This is Call to Action. Modern and recent browsers are moving towards this approach. | |
* They are requiring user action to deep link to the app. First is assigns click event | |
* to 'watch in app' link which will ultimately invoke DirectAppLaunchStrategy(). | |
* | |
* @param strategyParameters | |
* @constructor | |
*/ | |
var CTAAppLaunchStrategy = function(strategyParameters) { | |
AppLaunchStrategy.call(this, strategyParameters); | |
this.init = function() { | |
$("body").on('click', strategyParameters.getAppLauncherEl(), function(e) { | |
e.preventDefault(); | |
var directAppLaunchStrategy = new DirectAppLaunchStrategy(strategyParameters); | |
directAppLaunchStrategy.init() | |
}) | |
} | |
}; | |
/** | |
* There are few browsers on Android which does not support both app launch | |
* with 'intent' or 'custom uri scheme'. Not even CTA works on those browser. | |
* | |
* This just displays browser alert dialog box when 'Watch In App' button is clicked. | |
* @param strategyParameters | |
* @constructor | |
*/ | |
var AppLaunchNotSupportedStrategy = function(strategyParameters) { | |
AppLaunchStrategy.call(this, strategyParameters); | |
this.init = function () { | |
$("body").on('click', strategyParameters.getAppLauncherEl(), function(e) { | |
e.preventDefault(); | |
}) | |
} | |
}; | |
/** | |
* This is new deep linking technique introduced on IOS9. Same url is used to open app or in browser. | |
* If app is installed and configured to work with universal link, then automatically opens the app from | |
* browser, ios message app and other. | |
* | |
* For this to work: | |
* 1. Website should have valid Universal Link(json file in root of website) which defines path will be used for | |
* universal linking. Eg /deeplink/*, * | |
* 2. App should be specify the domains that can be used for universal linking to the app. Eg. | |
* https://www.linkedin.com, https://www.twitter.com. | |
* While app is installing it invokes the Universal like present on the domain specified and registers those path | |
* for universal linking. | |
* | |
* This technique will just change the url of 'Watch In App' button, to the url that does universal linking. | |
* | |
* @param strategyParameters | |
* @constructor | |
*/ | |
var UniversalLinkingAppLaunchStrategy = function(strategyParameters) { | |
AppLaunchStrategy.call(this, strategyParameters); | |
this.init = function() { | |
if (!strategyParameters.getUniversalLinkingUrl()) { | |
throw new Error('Universal Linking: Invalid url provided: ' + strategyParameters.getUniversalLinkingUrl()); | |
} | |
strategyParameters.getAppLauncherEl().attr('href', strategyParameters.getUniversalLinkingUrl()); | |
} | |
}; | |
/** | |
* Creates the Strategy Parameter object required for the Strategy | |
* based on the type of the OS. | |
* | |
* @return AppLaunchStrategyParameters Object | |
*/ | |
var AppLaunchParameterFactory = function() { | |
var strategyParameters = AppLaunchStrategyParameters; | |
var parameterType = 'desktop'; | |
if (BrowserChecker().isIOS) { | |
parameterType = 'ios'; | |
strategyParameters = IOSAppLaunchStrategyParameters; | |
} else if (BrowserChecker().isAndroid) { | |
parameterType = 'android'; | |
strategyParameters = AndroidAppLaunchStrategyParameters; | |
} | |
if (Settings.debug && strategyParameters) { | |
__debug("AppLaunchParameter" , parameterType); | |
} | |
return strategyParameters; | |
}; | |
/** | |
* This is a factory which creates the deep linking/App Launch Strategy/Technique based on the parameter. | |
* As each deep linking strategy requires different parameters, so parameter object should be provided | |
* based on the type of deeplinking strategy. | |
* | |
* @param strategyType | |
* @returns {*} | |
*/ | |
var AppLaunchStrategyFactory = function (strategyType) { | |
var strategyParameters = AppLaunchParameterFactory(); | |
var appLaunchStrategy; | |
if (strategyType == 'cta' || strategyType == undefined) { | |
appLaunchStrategy = new CTAAppLaunchStrategy(strategyParameters) | |
} else if(strategyType == 'direct') { | |
appLaunchStrategy = new DirectAppLaunchStrategy(strategyParameters) | |
} else if (strategyType == 'ul') { | |
appLaunchStrategy = new UniversalLinkingAppLaunchStrategy(strategyParameters) | |
} else if (strategyType == 'notsupported') { | |
appLaunchStrategy = new AppLaunchNotSupportedStrategy(strategyParameters) | |
} else { | |
throw new Error('Unsupported type'); | |
} | |
if (Settings.debug && appLaunchStrategy) { | |
__debug("AppLaunchStrategyType", strategyType); | |
} | |
return appLaunchStrategy; | |
}; | |
/** | |
* This is just a utility object which checks device OS, current browser and versions. | |
*/ | |
var BrowserChecker = function() { | |
var userAgent = window.navigator.userAgent.toLowerCase(); | |
var iOSVersion = function() { | |
return isIOS() ? parseInt(userAgent.match(/os\s+(\d)_/)[1], 10) : false; | |
}; | |
var isIOS = function() { | |
return /(?:i(?:phone|p(?:o|a)d))/.test(userAgent); | |
}; | |
var isFacebook = function () { | |
return !!userAgent.match(/FBAV/i); | |
}; | |
var isChrome = function () { | |
return userAgent.indexOf('chrome') > -1; | |
}; | |
var chromeVersion = function () { | |
var raw = userAgent.match(/chrom(e|ium)\/([0-9]+)\./); | |
return raw ? parseInt(raw[2], 10) : false; | |
}; | |
var isAndroid = function () { | |
/** | |
// This is to check Android Mobile. | |
return userAgent.indexOf('android') > -1 | |
&& userAgent.indexOf('Mozilla/5.0') > -1 | |
&& userAgent.indexOf('AppleWebKit') > -1; | |
*/ | |
return userAgent.indexOf('android') > -1; | |
}; | |
var androidVersion = function() { | |
var match = userAgent.match(/android\s([0-9\.]*)/); | |
return match ? parseFloat(match[1]) : false; | |
}; | |
// Downloaded from the Android Google play store. | |
var isAndroidStockBrowser = function() { | |
return isAndroid() | |
&& isChrome() | |
&& hasVersion() | |
&& isFacebook(); // FB users this browser, but deep links with custom URI scheme. | |
}; | |
// Default browser for old Android and android Apps. | |
var isAndroidNativeBrowser = function() { | |
// Facebook in Anroid 4.4 uses this browser, by default but does the deep linking, | |
// so lets check for facebook. | |
return !isFacebook() | |
&& (isAndroid() && (appleWebKitVersion() && appleWebKitVersion() < 537) | |
|| | |
(chromeVersion() && chromeVersion() < 37)); | |
}; | |
var hasVersion = function() { | |
return userAgent.indexOf('version') > -1; | |
}; | |
/** | |
* @return | |
* Returns object with following property | |
* - boolean : If AppleKit Browser | |
* - version : AppleKit Browser Version. | |
*/ | |
var appleWebKitVersion = function() { | |
var match = userAgent.match(/AppleWebKit\/([\d.]+)/); | |
return match ? parseFloat(match[1]) : false; | |
}; | |
return { | |
isIOS: isIOS(), | |
iOSVersion: iOSVersion(), | |
isAndroid: isAndroid(), | |
androidVersion: androidVersion(), | |
isAndroidStockBrowser: isAndroidStockBrowser(), | |
isAndroidNativeBrowser: isAndroidNativeBrowser(), | |
isFacebook: isFacebook(), | |
isChrome: isChrome() | |
} | |
}; | |
/** | |
* This is the main factory which creates the deeplinking strategy object based | |
* on the type of browser and its versions. | |
* | |
* @returns {AppLaunchStrategyFactory} | |
* @constructor | |
*/ | |
var AppLauncherFactory = function() { | |
var settings = Settings; | |
var browser = BrowserChecker(); | |
// Default strategy. | |
var deepLinkingStrategy = new AppLaunchStrategyFactory('cta'); | |
if (browser.isIOS) { | |
deepLinkingStrategy = new AppLaunchStrategyFactory('cta'); | |
if (browser.iOSVersion < 9) { | |
deepLinkingStrategy = new AppLaunchStrategyFactory('direct'); | |
} else { | |
deepLinkingStrategy = new AppLaunchStrategyFactory('ul'); | |
} | |
} | |
else if (browser.isAndroid) { | |
deepLinkingStrategy = new AppLaunchStrategyFactory('cta'); | |
if (browser.isAndroidNativeBrowser || browser.isAndroidStockBrowser) { | |
deepLinkingStrategy = new AppLaunchStrategyFactory('notsupported'); | |
} | |
} | |
if (Settings.debug == true) { | |
__debug('browser', browser); | |
} | |
return deepLinkingStrategy; | |
}; | |
/** | |
* This is Facade which initialized settings and App Launcher. | |
* This is the only method that is invoked externally. | |
* | |
* Check if cmp exists in the query string | |
* | |
* @param settings | |
* Entire settings info which NativeAppLauncher is dependent on. | |
*/ | |
var Init = function (settings) { | |
Settings = settings; | |
return AppLauncherFactory().init(); | |
}; | |
/** | |
* Print objects as string on console. Useful for debugging. | |
* @param name | |
* @param object | |
* @private | |
*/ | |
function __debug(name, object) { | |
console.log(name + ":" + JSON.stringify(object, null, 4)); | |
}; | |
/** | |
* Various utility functions. | |
*/ | |
var Utils = { | |
/** | |
* Returns query string into key/pair array. | |
* @param queryString string | |
* Raw Query string. | |
* @returns [] | |
*/ | |
getQueryString: function (queryString) { | |
if (queryString == undefined) { | |
queryString = window.location.search; | |
} | |
queryString = queryString.split('+').join(' '); | |
var params = {}, tokens, | |
re = /[?&]?([^=]+)=([^&]*)/g; | |
while (tokens = re.exec(queryString)) { | |
params[decodeURIComponent(tokens[1])] = decodeURIComponent(tokens[2]); | |
} | |
return params; | |
}, | |
appendQueryParameter: function (url, key, value) { | |
var re = new RegExp("([?&])" + key + "=.*?(&|$)", "i"); | |
var separator = url.indexOf('?') !== -1 ? "&" : "?"; | |
if (url.match(re)) { | |
return url.replace(re, '$1' + key + "=" + value + '$2'); | |
} | |
//TODO Filter value against XSS. | |
return url + separator + key + "=" + value; | |
}, | |
}; | |
return { | |
init: Init, | |
browserChecker: BrowserChecker, | |
util: Utils | |
}; | |
/** | |
* Sample client code which initialized the NativeAppLauncher with required settings. | |
$(document).ready(function() { | |
var settings = { | |
appLauncherElId: 'open-app-link', | |
notSupportedMessage: 'DeepLinking is not supported', | |
universalLinkUrl: 'http://localhost/deeplinking/', | |
appUri: 'go90://', | |
androidAppId: 'com.verizonmedia.go90.enterprise', | |
iOsAppStore:'https://itunes.apple.com/app/apple-store/id406387206?pt=2017933', | |
debug:true, | |
campaignKey:'cmp' | |
}; | |
NativeAppLauncher.init(settings); | |
}); | |
*/ | |
})(jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment