URL Scheme: Проверка наличия установленного приложения в Javascript

от автора

Недавно столкнулся с необходимостью определить, зарегистрирована ли URL Scheme в браузере, чтобы в зависимости от результата показывать либо кнопку загрузки приложения, либо прямой URL на его запуск.

Оказалось, что каких-либо стандартных механизмов для этого не существует. Но поскольку пользователи никак не хотели обращать внимания на кнопку Download и красную надпись о необходимости предварительной установки приложения, пришлось искать варианты. Об этом и пойдет речь ниже.

Сразу хочу предупредить, что не оказалось ни одного способа, сработавшего хотя бы в двух браузерах стабильно одинаково. Поэтому любителей красивых кроссбраузерных решений ждет разочарование — статья полна хаков (просьба не минусовать за это, самому тошно, но задача есть задача).

Для начала создадим наши ссылки.

<a class="runlink" href="myapp://command_line_parameters">Run</a> <a class="downloadlink" style="display:none;" href="http://mysite.com/download/app.exe">Download</a> 

Теперь создадим общую функцию, которая будет определять браузер и запускать соответствующий обработчик. Ну и еще одну вспомогательную, которая будет выводить запрос на загрузку приложения и загружать его в случае положительного ответа.

function initApplicationLink(runlink, downloadlink) { 	// Проверяем браузер 	var func = null; 	if (navigator.userAgent.indexOf('Firefox')>=0 ) func = checkFirefox; 	else if (navigator.userAgent.indexOf('Opera')>=0 ) func = checkOpera; 	else if (navigator.userAgent.indexOf('Chrome')>=0 ) func = checkChrome; 	else if (navigator.userAgent.indexOf('Safari')>=0 ) func = checkSafari; 	if ( func!=null ) { 		// Скрываем ссылку Download 		$(downloadlink).hide(); 		// Вешаем выбранную функцию на клик по ссылке запуска приложения  		$(runlink).on('click', function(){ 			func(runlink, downloadlink); 			// Отменяем нажатие ссылки, чтобы она не открылась в окне браузера 			return false; 		}); 	} 	else { 		// Для всех других браузеров просто показываем ссылку Download 		$(downloadlink).show(); 	} }  function downloadConfirmation(downloadlink) { 	if ( confirm('You need to install our application first. Do you really want to download it now?') ) 		document.location = $(downloadlink).attr('href'); } 

Начнем с Firefox и Opera, поскольку они в этом вопросе предоставляют 100% надежный и красивый механизм исключений. Но, к сожалению, реализация все равно отличается.

У Firefox самое простое решение.

function checkFirefox(runlink, downloadlink) { 	// Создаем скрытый фрейм, в котором пытаемся открыть наш URL 	var f = createFrame(); 	try { 		f.contentWindow.location = $(runlink).attr('href'); 	} 	catch (e) { 		// Если URL открыть не удалось, выводим запрос на загрузку приложения 		downloadConfirmation(downloadlink); 	} 	// Удаляем наш временный фрейм 	deleteFrame(f); } 

Opera не намного отличается от Firefox, за исключением того, что она отлавливает исключение не в момент попытки загрузки во фрейм URL с незарегистрированным протоколом, а в момент попытки обращения к не определенному атрибуту фрейма (contentWindow.location).

function checkOpera(runlink, downloadlink) { 	var f = createFrame(); 	f.contentWindow.location = $(runlink).attr('href'); 	setTimeout(function (){ 		try { 			// Пытаемся поработать с не определенным атрибутом фрейма 			// (вместо something можно использовать что-угодно) 			if ( f.contentWindow.location!='something' ) {} 		} 		catch (e) { 			downloadConfirmation(downloadlink); 		} 		deleteFrame(f); 	}, 0); } 

Функции createFrame() и deleteFrame() для Firefox и Opera:

function createFrame() { 	var f = document.createElement('iframe'); 	f.style.display = 'none'; 	return document.body.appendChild(f); }  function deleteFrame(f) { 	document.body.removeChild(f); } 

Safari под Windows тоже умеет ловить исключения, но со скрытым фреймом этот номер не прошел. Как вариант, можно использовать обычное окно. Решение не элегантное, но рабочее.

Safari под MacOS с исключениями, видимо, не дружит. Поэтому тут придется применить чисто костыльный метод. Фишка заключается в том, чтобы после запуска приложения проверить фокус нашего окна браузера. Если приложение успешно запустилось, то окно потеряло фокус, и в обработчике этого события мы зафиксировали этот факт. Если же приложение не открылось, то окно по-прежнему имеет фокус.

К сожалению, этот способ не на 100% стабилен. Например, если за время таймаута браузер не успел открыть приложение, или наоборот — пользователь успел закрыть приложение до наступления таймаута, и окно опять получило фокус. В результате, такие варианты выливаются в следующую неприятную картину. Всплывает окно с предложением загрузки, а через секунду стартует приложение. Или наоборот — стартует приложение, пользователь его тут же закрывает, и появляется окно с предложением загрузки. Поэтому увеличение или уменьшение таймаута почти одинаково плохо (увеличение все-таки немного лучше, т.к. в большинстве случаев пользователь не станет мгновенно закрывать только что открытое приложение). На практике наиболее приемлемым получился таймаут в одну секунду.

function checkSafari(runlink, downloadlink) { 	if ( navigator.userAgent.indexOf('Windows')>=0 ) { 		// Открываем маленькое окошко с нашим не стандартным URL 		var w = window.open($(runlink).attr('href'), '', 'width=50, height=50'); 		setTimeout(function(){ 			try { 				// Пытаемся поработать с не определенным атрибутом окна 				if ( w.location!='about:blank' ) {} 				w.close(); 				 window.focus(); 			} 			catch (e) { 				w.close(); 				window.focus(); 				downloadConfirmation(downloadlink); 			} 		}, 1000); 	} 	else { 		// Под MacOS и теоретически под iOS 		document.location = $(runlink).attr('href'); 		setTimeout(function(){ 			// Если окно по-прежнему в фокусе, значит приложение не запустилось 			if ( window.isFocused ) downloadConfirmation(downloadlink); 		}, 1000); 	} }  // Обработчики событий получения и потери фокуса окна браузера window.isFocused = true; $(window).on('focus', function(){ 	window.isFocused = true; }) .on('blur', function(){ 	window.isFocused = false; }); 

Chrome оказался полностью совместимым с Safari под MacOS, за исключением таймаута (Safari потребовалась почти секунда, чтобы запустить приложение, в то время, как Chrome запустил его меньше, чем за 250 миллисекунд).

(На практике способ с таймаутом очень сильно зависит от загруженности компьютера в момент запуска приложения. Я попадал на ситуации с Chrome и Safari, когда приложение не успевало запуститься, и выводилось окно с предложением о загрузке, после чего запускалось приложение. Опять-таки проблема с таймаутом, описанная выше).

function checkChrome(runlink, downloadlink) { 	document.location = $(runlink).attr('href'); 	setTimeout(function(){ 		if ( window.isFocused ) downloadConfirmation(downloadlink); 	}, 1000); } 

Ну и наконец Internet Explorer не показал стабильного результата при всех стараниях. Для IE технически подходит вариант Safari под Windows (с открытием маленького окна). Но получить хотя бы 50% стабильность так и не удалось. Поэтому IE ушел в ветку «другие браузеры», которая не делает никаких проверок, а просто отображает обе ссылки — на запуск и на загрузку приложения. (Буду признателен, если кто-то подскажет способ для IE).

Теперь нам осталось только проинициализировать наши ссылки.

initApplicationLink('.runlink', '.downloadlink'); 

Примечание: Решение тестировалось в последних версиях Chrome, Firefox, Opera, Safari и IE на платформах Windows и MacOS. В мобильных браузерах тесты не проводились, но вариант с таймаутом вполне может оказаться работоспособным на Android и iOS.

ссылка на оригинал статьи http://habrahabr.ru/post/182484/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *