Skip to content

Instantly share code, notes, and snippets.

@prabeengiri
Last active February 1, 2016 07:36
Show Gist options
  • Save prabeengiri/f7ab6c569f0ab15e3e30 to your computer and use it in GitHub Desktop.
Save prabeengiri/f7ab6c569f0ab15e3e30 to your computer and use it in GitHub Desktop.
AdvancedNativeAppLauncher
/**
*
* @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