- 記事一覧 >
- ブログ記事
カスタムURLスキームのアプリに対応しているか検知する方法
はじめに
URLリンクからアプリを起動できるカスタムURLスキーム(custom url scheme, custom protocol)というのがあります。
※英語サイトの場合、「custom protocol」と言っている場合が多いような気がします。
例として、microsft-edge:https://itccorporation.jp
とすると、Edgeが立ち上がり、https://itccorporation.jp
を表示します。
一方、例えば、microsft-edgeXXX:
に対応するアプリが無い場合、無反応になります。(Chromeの場合)
この場合、対応するアプリをインストールしないといけませんが、ユーザーは状況が分かりにくいです。
そこで、今回、対応するアプリの有無を検知する方法を実装しましたので、紹介していきたいと思います。
参考:https://github.com/ismailhabib/custom-protocol-detection
【検証環境】
Windows10 Pro 64bit
IE11 バージョン 20H2(OSビルド 19042, 1288)
Edge(Chromium) 95.0.1020.30
Chrome 95.0.4638.54
Firefox 93.0
Windows版Safari 5.0.5
Windows7 Professional Service Pack1
IE11 11.0.9600.18617 更新バージョン 11.0.40(KB4012204)
Firefox 63.0b14
【検証環境】以外では、この記事の内容と異なる可能性があります。
Safariについては、Windows版のみ確認しました。iOS(Mac,iPhone,iPad)の場合の挙動は不明です。Chromiumではない旧Edgeについては、全くの考慮外です。
対応するアプリの有無を検知する方法
方法
いきなり結論から先に書きますと、ブラウザによって、対応方法が異なり、以下の方法でできました。
●IENavigator.msLaunchUri
を使用。Navigator.msLaunchUri
が無いバージョンの場合、Safariと同じ対応。
●Chrome、Firefox(ver 64以上)、Edge(Chromium)
アプリケーションが開いた場合、アプリケーションの方へフォーカスが移るため、onBlur
(フォーカスを失ったときのイベント)が発生したかどうかで判定。
●Firefox(ver 64未満)
iframe、try
, catch
で"NS_ERROR_UNKNOWN_PROTOCOL"エラーを捕捉。
Firefoxはバージョン64.0から挙動が変わったようで、バージョン64未満(例:63.0b14)の場合、以下のように、iframe、try
, catch
で対応する必要がありました。逆にバージョン64以上の場合、以下の手法は使えず、Chromeと同じ対応が必要になりました。
var iframe = document.querySelector("#hiddenIframe");
if (!iframe) {
iframe = _createHiddenIframe(document.body, "about:blank");
}
try {
iframe.contentWindow.location.href = uri;
successCb();
} catch (e) {
if (e.name == "NS_ERROR_UNKNOWN_PROTOCOL") {
failCb();
}
}
●Safari
iframeでカスタムURLスキームを使ったURLを開くのと、onBlur
(フォーカスを失ったときのイベント)が発生したかどうかで判定。※iframeで開かないと、対応していない場合、画面遷移してしまう。
これにより、以下のように検知し、本来の動きの代わりにアラートを表示したりできます。
ソースコード
↑
この動きを実現した全ソースコードです。microsoft-edge:https://itccorporation.jp
は、古いWindowsの場合、関連付け設定されていなくて、エラー判定になると思います。foobar:https://itccorporation.jp
は、関連付け設定が必要ですので、後述します。
<!DOCTYPE html>
<html>
<head lang="ja">
<meta charset="UTF-8">
<title>Custom Protocol Detection</title>
</head>
<body>
<a href="microsoft-edge:https://itccorporation.jp" class="custom_protocol">microsoft-edge:https://itccorporation.jp</a><br />
<a href="microsoft-edgeXXX:https://itccorporation.jp" class="custom_protocol">microsoft-edgeXXX:https://itccorporation.jp</a><br />
<a href="foobar:https://itccorporation.jp" class="custom_protocol">foobar:https://itccorporation.jp</a><br />
<script>
Array.prototype.forEach.call(
document.querySelectorAll('.custom_protocol'),
function (elem) {
elem.addEventListener(
'click',
function (e) {
protocolCheck(this.getAttribute('href'), function () {
alert('protocol not recognized');
});
e.preventDefault ? e.preventDefault() : (e.returnValue = false);
},
false
);
}
);
function _registerEvent(target, eventType, cb) {
if (target.addEventListener) {
target.addEventListener(eventType, cb);
return {
remove: function () {
target.removeEventListener(eventType, cb);
},
};
} else {
target.attachEvent(eventType, cb);
return {
remove: function () {
target.detachEvent(eventType, cb);
},
};
}
}
function _createHiddenIframe(target, uri) {
var iframe = document.createElement('iframe');
iframe.src = uri;
iframe.id = 'hiddenIframe';
iframe.style.display = 'none';
target.appendChild(iframe);
return iframe;
}
function openUriWithHiddenFrame(uri, failCb, successCb) {
var timeout = setTimeout(function () {
failCb();
handler.remove();
}, 1000);
var iframe = document.querySelector('#hiddenIframe');
if (!iframe) {
iframe = _createHiddenIframe(document.body, 'about:blank');
}
var handler = _registerEvent(window, 'blur', onBlur);
function onBlur() {
clearTimeout(timeout);
handler.remove();
successCb();
}
iframe.contentWindow.location.href = uri;
}
function openUriWithTimeoutHack(uri, failCb, successCb) {
var timeout = setTimeout(function () {
failCb();
handler.remove();
}, 1000);
//handle page running in an iframe (blur must be registered with top level window)
var target = window;
while (target != target.parent) {
target = target.parent;
}
var handler = _registerEvent(target, 'blur', onBlur);
function onBlur() {
clearTimeout(timeout);
handler.remove();
successCb();
}
window.location = uri;
}
function openUriUsingFirefox(uri, failCb, successCb) {
console.log('openUriUsingFirefox');
var iframe = document.querySelector('#hiddenIframe');
if (!iframe) {
iframe = _createHiddenIframe(document.body, 'about:blank');
}
try {
iframe.contentWindow.location.href = uri;
successCb();
} catch (e) {
if (e.name == 'NS_ERROR_UNKNOWN_PROTOCOL') {
failCb();
}
}
}
function openUriUsingIEInOlderWindows(uri, failCb, successCb) {
if (getInternetExplorerVersion() === 10) {
openUriUsingIE10InWindows7(uri, failCb, successCb);
} else if (
getInternetExplorerVersion() === 9 ||
getInternetExplorerVersion() === 11
) {
openUriWithHiddenFrame(uri, failCb, successCb);
} else {
openUriInNewWindowHack(uri, failCb, successCb);
}
}
function openUriUsingIE10InWindows7(uri, failCb, successCb) {
var timeout = setTimeout(failCb, 1000);
window.addEventListener('blur', function () {
clearTimeout(timeout);
successCb();
});
var iframe = document.querySelector('#hiddenIframe');
if (!iframe) {
iframe = _createHiddenIframe(document.body, 'about:blank');
}
try {
iframe.contentWindow.location.href = uri;
} catch (e) {
failCb();
clearTimeout(timeout);
}
}
function openUriInNewWindowHack(uri, failCb, successCb) {
var myWindow = window.open('', '', 'width=0,height=0');
myWindow.document.write("<iframe src='" + uri + "'></iframe>");
setTimeout(function () {
try {
myWindow.location.href;
myWindow.setTimeout('window.close()', 1000);
successCb();
} catch (e) {
myWindow.close();
failCb();
}
}, 1000);
}
function openUriWithMsLaunchUri(uri, failCb, successCb) {
navigator.msLaunchUri(uri, successCb, failCb);
}
function checkBrowser() {
var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
var ua = navigator.userAgent.toLowerCase();
return {
isOpera: isOpera,
isFirefox_OLD:
typeof InstallTrigger !== 'undefined' &&
parseInt(
'' +
navigator.userAgent.substring(
navigator.userAgent.indexOf('Firefox') + 8
),
10
) < 64,
isFirefox:
typeof InstallTrigger !== 'undefined' &&
parseInt(
'' +
navigator.userAgent.substring(
navigator.userAgent.indexOf('Firefox') + 8
),
10
) >= 64,
isSafari:
(~ua.indexOf('safari') && !~ua.indexOf('chrome')) ||
Object.prototype.toString
.call(window.HTMLElement)
.indexOf('Constructor') > 0,
isIOS: /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream,
isChrome: !!window.chrome && !isOpera,
isIE: /*@cc_on!@*/ false || !!document.documentMode, // At least IE6
};
}
function getInternetExplorerVersion() {
var rv = -1;
if (navigator.appName === 'Microsoft Internet Explorer') {
var ua = navigator.userAgent;
var re = new RegExp('MSIE ([0-9]{1,}[.0-9]{0,})');
if (re.exec(ua) != null) rv = parseFloat(RegExp.$1);
} else if (navigator.appName === 'Netscape') {
var ua = navigator.userAgent;
var re = new RegExp('Trident/.*rv:([0-9]{1,}[.0-9]{0,})');
if (re.exec(ua) != null) {
rv = parseFloat(RegExp.$1);
}
}
return rv;
}
function protocolCheck(uri, failCb, successCb, unsupportedCb) {
function failCallback() {
failCb && failCb();
}
function successCallback() {
successCb && successCb();
}
if (navigator.msLaunchUri) {
//for IE and Edge in Win 8 and Win 10
openUriWithMsLaunchUri(uri, failCb, successCb);
} else {
var browser = checkBrowser();
if (browser.isFirefox_OLD) {
openUriUsingFirefox(uri, failCallback, successCallback);
} else if (browser.isFirefox || browser.isChrome || browser.isIOS) {
openUriWithTimeoutHack(uri, failCallback, successCallback);
} else if (browser.isIE) {
openUriUsingIEInOlderWindows(uri, failCallback, successCallback);
} else if (browser.isSafari) {
openUriWithHiddenFrame(uri, failCallback, successCallback);
} else {
unsupportedCb();
//not supported, implement please
}
}
}
</script>
</body>
</html>
対応していない場合の挙動
<a href="microsoft-edgexxx:https://itccorporation.jp">microsoft-edgexxx</a>
のように、hrefが関連付け設定されていなくて対応していないカスタムURLスキームの場合、どうなるのか、検証してみました。
おのおの以下の挙動になりました。
●IE(Navigator.msLaunchUri
が使用可能なバージョン)
アプリストアへ遷移を促す画面が表示されます。(外側をクリックするとキャンセル可)
●IE(Navigator.msLaunchUri
が無いバージョン)
「この Web ページは表示できません」の画面に切り替わります。
●Chrome、Firefox(ver 64以上)、Edge(Chromium)
リンクが無反応になります。
●Firefox(ver 64未満)
「アドレスのプロトコルが不明です」の画面に切り替わります。
●Safari
「指定されたアドレスを開けません。」の画面に切り替わります。
独自カスタムURLスキーム追加
<a href="foobar:https://itccorporation.jp" class="custom_protocol">foobar:https://itccorporation.jp</a>
のように独自のカスタムURLスキームを追加するときの手順です。
追加手順
その1
設定→アプリ→既定のアプリ
既定のアプリを下の方にスクロールして、「プロトコルごとに既定のアプリを選ぶ」をクリックします。
Windows7の場合は、コントロールパネルから「プログラム」→「既定のプログラム」→「ファイルの種類またはプロトコルのプログラムへの関連付け」です。」
プロトコル名の右側をクリックすると、アプリを変更できます。
ただし、この場合、新規プロトコル追加ができず、決められた範囲のアプリからしか選択できません。
レジストリを解析して、ここに独自のプロトコルを表示させようとしてみましたが、良く分かりませんでした。
その2
レジストリを書き換えます。
以下の内容を .reg ファイル(例:foobar.reg
)に保存し、ダブルクリックすると、foobar
というプロトコルが追加されて、foobar:https://itccorporation.jp
のように独自のカスタムURLスキームが使えるようになります。
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\foobar]
@="URL:foobar"
"URL Protocol"=""
[HKEY_CLASSES_ROOT\foobar\shell]
[HKEY_CLASSES_ROOT\foobar\shell\open]
[HKEY_CLASSES_ROOT\foobar\shell\open\command]
@="\"C:\\Users\\admin\\Desktop\\msg.bat\" %1"
[HKEY_CLASSES_ROOT\foobar\shell\open\command]
に指定されている、@="\"C:\\Users\\admin\\Desktop\\msg.bat\" %1"
は、エスケープされているので、分かりにくいですが、"C:\Users\admin\Desktop\msg.bat" %1
と引数をバッチファイルに渡して起動しています。
regedit.exe
で手動で登録しても同じことです。
msg.bat
は、引数の内容をダイアログに表示するバッチファイルです。バッチファイルだけでは、ダイアログを表示できないため、vbsを生成して、起動し、削除しています。
@echo off
echo MsgBox "%1",vbInformation,"info" > %TEMP%\msgbox.vbs & %TEMP%\msgbox.vbs
del /Q %TEMP%\msgbox.vbs
引数%1について
foobar:https://itccorporation.jp
↓"C:\Users\admin\Desktop\msg.bat" %1
のように起動した場合、
%1 は、foobar:https://itccorporation.jp
です。
chrome.exe
にfoobar:https://itccorporation.jp
を渡しても、https://itccorporation.jp
を開いてくれません。https://itccorporation.jp
を渡したい場合、以下のようにcmd.exeで foobar:
部分を取り除くしか方法は無いようです。
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\foobar]
@="URL:foobar"
"URL Protocol"=""
[HKEY_CLASSES_ROOT\foobar\shell]
[HKEY_CLASSES_ROOT\foobar\shell\open]
[HKEY_CLASSES_ROOT\foobar\shell\open\command]
@="C:\\Windows\\System32\\cmd.exe /k set myvar=%1&call set myvar=%%myvar:foobar:=%%&call start chrome.exe %%myvar%%"
microsoft-edge:について
プロトコル microsoft-edge:
について、HKEY_CLASSES_ROOT\MSEdgeHTM\shell\open\command
に"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" --single-argument %1
が登録されています。
microsoft-edge:https://itccorporation.jp
で起動した場合、
%1 は、microsoft-edge:https://itccorporation.jp
で、
"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" --single-argument microsoft-edge:https://itccorporation.jp
の意味になり、URL=https://itccorporation.jp
の状態で、Edgeが開いてきます。
一方で、
プロトコル foobar:
に
"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" --single-argument %1
を登録
↓foobar:https://itccorporation.jp
で起動した場合、
%1 は、foobar:https://itccorporation.jp
で、
URL=https://itccorporation.jp
の状態で開きません。URLがfoobar:で始まるからです。microsoft-edge:
のときだけ、Edgeがその後にURLが指定されているとみなして、引数のmicrosoft-edge:
を無視しているようです。
ちなみに、Edge自身からは、microsoft-edge:https://itccorporation.jp
でEdgeが開かなくて、無反応になります。
その他、宣伝、誹謗中傷等、当方が不適切と判断した書き込みは、理由の如何を問わず、投稿者に断りなく削除します。
書き込み内容について、一切の責任を負いません。
このコメント機能は、予告無く廃止する可能性があります。ご了承ください。
コメントの削除をご依頼の場合はTwitterのDM等でご連絡ください。