Facebook Javascript SDK login problems in Chrome/Safari (webkit) - Unsafe Javascript attempt - javascript

Similar questions have been asked, but the solutions to those problems don't seem to help me.
I've got a local development site set up running on a virtual machine: http://lamp.site
When the index.php loads, I've got this javascript running (with the correct appId):
/* facebook auth */
window.fbAsyncInit = function() {
FB.init({
appId: '01234567890',
channelUrl: 'http://lamp.site/channel.html'
});
};
// Load the SDK Asynchronously
(function(d){
var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
if (d.getElementById(id)) {return;}
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
ref.parentNode.insertBefore(js, ref);
}(document));
Then when a button is clicked, this javascript function is triggered:
#HTML
<li><a class="btn" href="#" ng-click="connectToFacebook()">Connect with Facebook</a></li>
#Snippet in AngularJS Controller
$scope.connectToFacebook = function() {
facebookConnect.authenticate(
function(reason) { // fail
console.log(reason);
},
function(user) { // success
console.log('logged in',user);
//Session.login();
});
return false;
}
#Factory in AngularJS module
.factory('facebookConnect', function() {
return {
authenticate: function(fail, success) {
FB.login(function(response) {
console.log(response);
if (response.authResponse) {
FB.api('/me', success);
} else {
fail('User cancelled login or did not fully authorize.');
}
});
}
}
If I press the button in Firefox, it works! Lovely, I get all the right things back from Facebook.
If I use Chrome or Safari on my PC, or Safari Mobile on my iPhone, I get the facebook login window popup with the message "An error occurred. Please try again later.", and the following console data:
Unsafe JavaScript attempt to access frame with URL
https://www.facebook.com/dialog/permissions.request from frame with URL
http://lamp.site/index.php?page=login. Domains, protocols and ports must match.
Object
authResponse: null
status: "not_authorized"
__proto__: Object
User cancelled login or did not fully authorize.
I've got my local testing URL added into the facebook app in the developer section, that works because it logs in with Firefox. This is my channels file, stored at http://lamp.site/channel.html
<script src="http://connect.facebook.net/en_US/all.js"></script>
I've tried having the channel file with and without http: in the src= tag.
The facebook app has the following settings:
App domains: lamp.site
Site URL: http://lamp.site
Mobile site URL: http://lamp.site
So it works in Firefox, what the hell am I doing wrong. I can't just enable cross site scripting as users wouldn't do that and this is going into a mobile site.
Anyone else managed to solve this problem recently?
All the other similar questions have gone unanswered... someone must have fixed this!
EDIT: I've made a simplified version, new app ID, on my website.
http://dev.willshawmedia.com/fb/
Here is the screen shot with the app ID from Facebook Dev application panel
You can look at the source code, it's copied straight from here:
https://developers.facebook.com/docs/guides/web/
And the channel file does exist:
http://dev.willshawmedia.com/fb/channel.html
I can't make it any simpler, I still get the "An error occurred. Please try again later." message, but now it just forwards onto Facebook instead of authenticating and closing.
Edit: I was missing the site URLs, I've added those, the simple example is working.
As it's working on my live site, it must be to do with my local domains. But I've got this line in my /etc/hosts/ file:
192.168.0.13 lamp.site
That's the IP address of the VirtualBox Ubuntu server running on my laptop. I can browse that site fine.

Sandbox Mode must be disabled for it to work in Chrome. That's it, that's all it took.
I enabled that mode as I thought that would stop it showing up in searches, which I don't want. And as it worked in Firefox I couldn't see that being a problem. But there you go, I switched off sandbox mode and it started working immediately.
I don't under stand that why that is stopping Chrome though.

Your factory has some strange JavaScript: return new function() ... outside of that I don't see an issue. I'm using this same basic technique and I don't have any problems in Chrome.
Be sure your Facebook application is set up to your domain (let's call it "mydomain.com")... THEN, to test locally, you'll need to edit your hosts file to point mydomain.com to 127.0.0.1... so the URL in the browser location matches what Facebook is expecting.
That's probably the only difference between what I'm doing and what you're doing.
... also make sure your App ID is the proper App ID from setting up your Facebook Application. Double check it. BS "test" App ID's will give you the error message as well.
-
-
EDIT : on the new function(){ } front... which I don't think is your problem.... I want to explain a little more:
var x = new function() { this.foo = 'test'; };
is the same as
var x = { foo: 'test' };
Because the new keyword is used to call a function as an object constructor, and you're using new on an anonymous function that you can't use again, so there is no reason to declare it.
So your snippet could (and probably should) be:
.factory('facebookConnect', function() {
return {
authenticate: function(fail, success) {
FB.login(function(response) {
console.log(response);
if (response.authResponse) {
FB.api('/me', success);
} else {
fail('User cancelled login or did not fully authorize.');
}
});
}
}
});

Related

iron-router: How to load a route completely just as when hitting refresh after loading a route?

I have a sign-in method in my Meteor application that redirects users to different routes after login in to the system. Here is my method:
Meteor.loginWithPassword(emailVar, passwordVar, function (err) {
if (err !== undefined) {
// error handling code
} else {
if (!Roles.userIsInRole(Meteor.userId(), 'active')) {
return Router.go('account-deactivated');
}
if (Roles.userIsInRole(Meteor.userId(), 'pharmacist')) {
return Router.go('pharmacist-dashboard');
}
if (Roles.userIsInRole(Meteor.userId(), 'admin')) {
return Router.go('admin-dashboard');
}
}
});
While this method works as expected, it produces some issues with my theme (AdminLTE) due to JavaScript loading problems (ex: app.min.js etc.). For example the sliding effects doesn't work on redirected page. But when I reload the page from the browser it starts to work as expected.
I know this is a separate issue that needs to be addressed. But if there is a way to completely reload a link in Meteor using iron-router it would be helpful. Specially when a page is transfered to a completely different user environment where a new set of JavaScript and CSS are used.
I went through the user documentations of iron-router but the fixes do not provide a solution.
Try using window.location.href to redirect.
Using Router.go is merely loading the template for the route you are linking to, whereas using window.location.href is loading the url as if it was a link you just clicked (actual refresh).
You'll need to use the actual url though, not the 'route name'.
window.location.href = "http://yourapp.com/route/here";

deployJava.js - opening in a new window

I am using deployJava.js from Oracle's deployment toolkit to deploy and run an applet. If java is not installed, the current page is redirected to the download Java Page. I do want to redirect the user to the download Java Page, however, I would like this to happen in a new window. Redirecting it in the same page means my session is lost which I don't want happening. Is there a way to do this?
Give this a try:
Oracle provides a human-readable version of the js file here: https://www.java.com/js/deployJava.txt
EDIT: [
Change the location.href page navigation code within here:
IEInstall: function() {
location.href = constructGetJavaURL(
...);
return false;
},
done: function (name, result) {
},
FFInstall: function() {
location.href = constructGetJavaURL(
...);
return false;
},
to these:
window.open(constructGetJavaURL(...));
You might have to mess around with that code a bit, I can't try it out myself. ]

Detecting protocol handler with Javascript [duplicate]

I have created a custom URL protocol handler.
http://
mailto://
custom://
I have registered a WinForms application to respond accordingly. This all works great.
But I would like to be able to gracefully handle the case where the user doesn't have the custom URL protocol handler installed, yet.
In order to be able to do this I need to be able to detect the browser's registered protocol handlers, I would assume from JavaScript. But I have been unable to find a way to poll for the information. I am hoping to find a solution to this problem.
Thanks for any ideas you might be able to share.
This would be a very, very hacky way to do this... but would this work?
Put the link in as normal...
But attach an onclick handler to it, that sets a timer and adds an onblur handler for the window
(in theory) if the browser handles the link (application X) will load stealing the focus from the window...
If the onblur event fires, clear the timer...
Otherwise in 3-5seconds let your timeout fire... and notify the user "Hmm, looks like you don't have the Mega Uber Cool Application installed... would you like to install it now? (Ok) (Cancel)"
Far from bulletproof... but it might help?
There's no great cross-browser way to do this. In IE10+ on Win8+, a new msLaunchUri api enables you to launch a protocol, like so:
navigator.msLaunchUri('skype:123456',
function()
{
alert('success');
},
function()
{
alert('failed');
}
);
If the protocol is not installed, the failure callback will fire. Otherwise, the protocol will launch and the success callback will fire.
I discuss this topic a bit further here:
https://web.archive.org/web/20180308105244/https://blogs.msdn.microsoft.com/ieinternals/2011/07/13/understanding-protocols/
This topic is of recent (2021) interest; see https://github.com/fingerprintjs/external-protocol-flooding for discussion.
HTML5 defines Custom scheme and content handlers (to my knowledge Firefox is the only implementor so far), but unfortunately there is currently no way to check if a handler already exists—it has been proposed, but there was no follow-up. This seems like a critical feature to use custom handlers effectively and we as developers should bring attention to this issue in order to get it implemented.
There seems to be no straightforward way via javascript to detect the presence of an installed app that has registered a protocol handler.
In the iTunes model, Apple provides urls to their servers, which then provide pages that run some javascript:
http://ax.itunes.apple.com/detection/itmsCheck.js
So the iTunes installer apparently deploys plugins for the major browsers, whose presence can then be detected.
If your plugin is installed, then you can be reasonably sure that redirecting to your app-specific url will succeed.
What seams the most easy solution is to ask the user the first time.
Using a Javascript confirm dialog per example:
You need this software to be able to read this link. Did you install it ?
if yes: create a cookie to not ask next time; return false and the link applies
if false: window.location.href = '/downloadpage/'
If you have control of the program you're trying to run (the code), one way to see if the user was successful in running the application would be to:
Before trying to open the custom protocol, make an AJAX request to a server script that saves the user's intent in a database (for example, save the userid and what he wanted to do).
Try to open the program, and pass on the intent data.
Have the program make a request to the server to remove the database entry (using the intent data to find the correct row).
Make the javascript poll the server for a while to see if the database entry is gone. If the entry is gone, you'll know the user was successful in opening the application, otherwise the entry will remain (you can remove it later with cronjob).
I have not tried this method, just thought it.
I was able to finally get a cross-browser (Chrome 32, Firefox 27, IE 11, Safari 6) solution working with a combination of this and a super-simple Safari extension. Much of this solution has been mentioned in one way or another in this and this other question.
Here's the script:
function launchCustomProtocol(elem, url, callback) {
var iframe, myWindow, success = false;
if (Browser.name === "Internet Explorer") {
myWindow = window.open('', '', 'width=0,height=0');
myWindow.document.write("<iframe src='" + url + "'></iframe>");
setTimeout(function () {
try {
myWindow.location.href;
success = true;
} catch (ex) {
console.log(ex);
}
if (success) {
myWindow.setTimeout('window.close()', 100);
} else {
myWindow.close();
}
callback(success);
}, 100);
} else if (Browser.name === "Firefox") {
try {
iframe = $("<iframe />");
iframe.css({"display": "none"});
iframe.appendTo("body");
iframe[0].contentWindow.location.href = url;
success = true;
} catch (ex) {
success = false;
}
iframe.remove();
callback(success);
} else if (Browser.name === "Chrome") {
elem.css({"outline": 0});
elem.attr("tabindex", "1");
elem.focus();
elem.blur(function () {
success = true;
callback(true); // true
});
location.href = url;
setTimeout(function () {
elem.off('blur');
elem.removeAttr("tabindex");
if (!success) {
callback(false); // false
}
}, 1000);
} else if (Browser.name === "Safari") {
if (myappinstalledflag) {
location.href = url;
success = true;
} else {
success = false;
}
callback(success);
}
}
The Safari extension was easy to implement. It consisted of a single line of injection script:
myinject.js:
window.postMessage("myappinstalled", window.location.origin);
Then in the web page JavaScript, you need to first register the message event and set a flag if the message is received:
window.addEventListener('message', function (msg) {
if (msg.data === "myappinstalled") {
myappinstalledflag = true;
}
}, false);
This assumes the application which is associated with the custom protocol will manage the installation of the Safari extension.
In all cases, if the callback returns false, you know to inform the user that the application (i.e., it's custom protocol) is not installed.
You say you need to detect the browser's protocol handlers - do you really?
What if you did something like what happens when you download a file from sourceforge? Let's say you want to open myapp://something. Instead of simply creating a link to it, create a link to another HTML page accessed via HTTP. Then, on that page, say that you're attempting to open the application for them. If it doesn't work, they need to install your application, which they can do by clicking on the link you'll provide. If it does work, then you're all set.
This was a recommended approach for IE by Microsoft support
http://msdn.microsoft.com/en-us/library/ms537503%28VS.85%29.aspx#related_topics
"If you have some control over the binaries being installed on a user’s machine, checking the UA in script seems like a relevant approach:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\5.0\User Agent\Post Platform
" -- By M$ support
Every web page has access to the userAgent string and if you drop a custom post platform value, detecting this in javascript using navigator.userAgent is quite simple.
Fortunately, other major browsers like Firefox and Chrome (barring Safari :( ), do not throw "page not found" errors when a link with a custom protocol is clicked and the protocol is not installed on the users machine. IE is very unforgiving here, any trick to click in a invisible frame or trap javascript errors does not work and ends up with ugly "webpage cannot be displayed" error. The trick we use in our case is to inform users with browser specific images that clicking on the custom protocol link will open an application. And if they do not find the app opening up, they can click on an "install" page. In terms of XD this wprks way better than the ActiveX approach for IE.
For FF and Chrome, just go ahead and launch the custom protocol without any detection. Let the user tell you what he sees.
For Safari, :( no answers yet
I'm trying to do something similar and I just discovered a trick that works with Firefox. If you combine it with the trick for IE you can have one that works on both main browsers (I'm not sure if it works in Safari and I know it doesn't work in Chrome)
if (navigator.appName=="Microsoft Internet Explorer" && document.getElementById("testprotocollink").protocolLong=="Unknown Protocol") {
alert("No handler registered");
} else {
try {
window.location = "custom://stuff";
} catch(err) {
if (err.toString().search("NS_ERROR_UNKNOWN_PROTOCOL") != -1) {
alert("No handler registered");
}
}
}
In order for this to work you also need to have a hidden link somewhere on the page, like this:
<a id="testprotocollink" href="custom://testprotocol" style="display: none;">testprotocollink</a>
It's a bit hacky but it works. The Firefox version unfortunately still pops up the default alert that comes up when you try to visit a link with an unknown protocol, but it will run your code after the alert is dismissed.
You can try something like this:
function OpenCustomLink(link) {
var w = window.open(link, 'xyz', 'status=0,toolbar=0,menubar=0,height=0,width=0,top=-10,left=-10');
if(w == null) {
//Work Fine
}
else {
w.close();
if (confirm('You Need a Custom Program. Do you want to install?')) {
window.location = 'SetupCustomProtocol.exe'; //URL for installer
}
}
}
This is not a trivial task; one option might be to use signed code, which you could leverage to access the registry and/or filesystem (please note that this is a very expensive option). There is also no unified API or specification for code signing, so you would be required to generate specific code for each target browser. A support nightmare.
Also, I know that Steam, the gaming content delivery system, doesn't seem to have this problem solved either.
Here's another hacky answer that would require (hopefully light) modification to your application to 'phone home' on launch.
User clicks link, which attempts to launch the application. A unique
identifier is put in the link, so that it's passed to the
application when it launches. Web app shows a spinner or something of that nature.
Web page then starts checking for a
'application phone home' event from an app with this same unique ID.
When launched, your application does an HTTP post to your web app
with the unique identifier, to indicate presence.
Either the web page sees that the application launched, eventually, or moves on with a 'please download' page.

How to do FB.ui({ method: 'auth.login' ...) with display: 'iframe' instead of 'popup'?

I'm working on a login for a simple Facebook app. I'm able to use the JavaScript SDK to successfully present a login / extended permissions dialog in a popup window with either FB.login or the following code:
FB.ui({ method: 'auth.login',
perms: 'read_stream,publish_stream',
display: 'popup' },
function (rsp) {
fg_log('on login');
if(rsp.session) {
if(rsp.perms) {
fg_log('PERMS: ',rsp.perms);
} else {
fg_log('Hmm. No permissions');
}
} else {
fg_log('Hmm. No login');
}
}
);
The problem is... I don't like popup windows much. From a UI standpoint, I think they feel off, like they don't belong to the rest of the app. And getting them to show up via JavaScript also require an extra click from the user for no reason -- in order to get around popup blockers, the user has to click on something like a login button (largely pointless, given that by the time the app knows it needs to display a login button, it already knows the user needs to log in and may as well just present the permissions dialog).
So, I thought, why not an iframe instead? No issues with popup blockers, embedded nicely in the page, and Facebook seems to love 'em.
A little digging in the recent (2.1.2) JavaScript SDK source and various other posts on the Facebook developers forum seems to indicate one can pass "display: 'iframe'" as part of the options to FB.ui.
But when I try it, though the iframe does come up, instead of getting the permissions dialog, I get:
"An error occurred with . Please try again later."
(Note: trying again later produces the same results.)
is there a trick to get this to work, or is it forbidden for some reason?
Try using FB.login instead of FB..ui. If the user is already logged in, and granted the permissions you are asking for via FB.login, then there is no dialog. Otherwise an "inline" one is displayed requesting the extra permissions/login.
It's a little counterintuitive to use a login function to get more permissions when the user is already logged in. But it works.
Not possible anymore (Jul 2014), but you always could and still can create your own iframe and fill it with a page that redirects from your server to a full page FB login.
See https://developers.facebook.com/docs/reference/dialogs/oauth/ :
If you are using the URL redirect dialog implementation, then this
will be a full page display, shown within Facebook.com. This display
type is called page.
The FB iframe worked at the time the question was asked by either using display: 'iframe' with FB.ui() as Mustafa suggested or using FB.login() (at some points in time it defaulted to 'dialog' mode if FB was properly inited, other times you had to display mode as well).
This was turned off, most likely early 2014 & due to clickjacking. From the reference linked above:
If you are using the JavaScript SDK, this will default to a popup
window. You can also force the popup or page types when using the
JavaScript SDK, if necessary. iframe and async types are not valid for
the Login Dialog for security reasons.
You can try this:
I use following method approx 6 month ago. :)
<div>
outh dialog
</div>
<div id="fb-root"></div>
<script>
base_url = 'http/s://path/to/your/site/url'; //Which is set into app setting
myOuthDialog = function(){
FB.ui({method: 'oauth',
client_id:'<!--YOUR APP ID-->',
api_key:'<!--YOUR APP ID-->',
app_id:'<!--YOUR APP ID-->',
canvas:'1',
fbconnect:'1',
response_type:'code token',
perms:'email',
scope:'email',
redirect_uri:base_url,
display:'iframe'
}, myCallback);
}
myCallback = function(data){
FB.getLoginStatus(function(response) {
if (response.status === 'connected') {
document.location = base_url;
var uid = response.authResponse.userID;
var accessToken = response.authResponse.accessToken;
} else if (response.status === 'not_authorized') {
myOuthDialog();
} else {
document.location = base_url;
}
}, true);
}
</script>
<script>
window.fbAsyncInit = function() {
FB.init({
appId : '<!--YOUR APP ID-->', // App ID
channelUrl : base_url+'channel.php', // Channel File
status : true, // check login status
cookie : true, // enable cookies to allow the server to access the session
oauth : true, // enable OAuth 2.0
xfbml : true // parse XFBML
});
};
(function(d){
var js, id = 'facebook-jssdk'; if (d.getElementById(id)) {return;}
js = d.createElement('script'); js.id = id; js.async = true;
js.src = "//connect.facebook.net/en_US/all.js";
d.getElementsByTagName('head')[0].appendChild(js);
}(document));
</script>

How to detect browser's protocol handlers?

I have created a custom URL protocol handler.
http://
mailto://
custom://
I have registered a WinForms application to respond accordingly. This all works great.
But I would like to be able to gracefully handle the case where the user doesn't have the custom URL protocol handler installed, yet.
In order to be able to do this I need to be able to detect the browser's registered protocol handlers, I would assume from JavaScript. But I have been unable to find a way to poll for the information. I am hoping to find a solution to this problem.
Thanks for any ideas you might be able to share.
This would be a very, very hacky way to do this... but would this work?
Put the link in as normal...
But attach an onclick handler to it, that sets a timer and adds an onblur handler for the window
(in theory) if the browser handles the link (application X) will load stealing the focus from the window...
If the onblur event fires, clear the timer...
Otherwise in 3-5seconds let your timeout fire... and notify the user "Hmm, looks like you don't have the Mega Uber Cool Application installed... would you like to install it now? (Ok) (Cancel)"
Far from bulletproof... but it might help?
There's no great cross-browser way to do this. In IE10+ on Win8+, a new msLaunchUri api enables you to launch a protocol, like so:
navigator.msLaunchUri('skype:123456',
function()
{
alert('success');
},
function()
{
alert('failed');
}
);
If the protocol is not installed, the failure callback will fire. Otherwise, the protocol will launch and the success callback will fire.
I discuss this topic a bit further here:
https://web.archive.org/web/20180308105244/https://blogs.msdn.microsoft.com/ieinternals/2011/07/13/understanding-protocols/
This topic is of recent (2021) interest; see https://github.com/fingerprintjs/external-protocol-flooding for discussion.
HTML5 defines Custom scheme and content handlers (to my knowledge Firefox is the only implementor so far), but unfortunately there is currently no way to check if a handler already exists—it has been proposed, but there was no follow-up. This seems like a critical feature to use custom handlers effectively and we as developers should bring attention to this issue in order to get it implemented.
There seems to be no straightforward way via javascript to detect the presence of an installed app that has registered a protocol handler.
In the iTunes model, Apple provides urls to their servers, which then provide pages that run some javascript:
http://ax.itunes.apple.com/detection/itmsCheck.js
So the iTunes installer apparently deploys plugins for the major browsers, whose presence can then be detected.
If your plugin is installed, then you can be reasonably sure that redirecting to your app-specific url will succeed.
What seams the most easy solution is to ask the user the first time.
Using a Javascript confirm dialog per example:
You need this software to be able to read this link. Did you install it ?
if yes: create a cookie to not ask next time; return false and the link applies
if false: window.location.href = '/downloadpage/'
If you have control of the program you're trying to run (the code), one way to see if the user was successful in running the application would be to:
Before trying to open the custom protocol, make an AJAX request to a server script that saves the user's intent in a database (for example, save the userid and what he wanted to do).
Try to open the program, and pass on the intent data.
Have the program make a request to the server to remove the database entry (using the intent data to find the correct row).
Make the javascript poll the server for a while to see if the database entry is gone. If the entry is gone, you'll know the user was successful in opening the application, otherwise the entry will remain (you can remove it later with cronjob).
I have not tried this method, just thought it.
I was able to finally get a cross-browser (Chrome 32, Firefox 27, IE 11, Safari 6) solution working with a combination of this and a super-simple Safari extension. Much of this solution has been mentioned in one way or another in this and this other question.
Here's the script:
function launchCustomProtocol(elem, url, callback) {
var iframe, myWindow, success = false;
if (Browser.name === "Internet Explorer") {
myWindow = window.open('', '', 'width=0,height=0');
myWindow.document.write("<iframe src='" + url + "'></iframe>");
setTimeout(function () {
try {
myWindow.location.href;
success = true;
} catch (ex) {
console.log(ex);
}
if (success) {
myWindow.setTimeout('window.close()', 100);
} else {
myWindow.close();
}
callback(success);
}, 100);
} else if (Browser.name === "Firefox") {
try {
iframe = $("<iframe />");
iframe.css({"display": "none"});
iframe.appendTo("body");
iframe[0].contentWindow.location.href = url;
success = true;
} catch (ex) {
success = false;
}
iframe.remove();
callback(success);
} else if (Browser.name === "Chrome") {
elem.css({"outline": 0});
elem.attr("tabindex", "1");
elem.focus();
elem.blur(function () {
success = true;
callback(true); // true
});
location.href = url;
setTimeout(function () {
elem.off('blur');
elem.removeAttr("tabindex");
if (!success) {
callback(false); // false
}
}, 1000);
} else if (Browser.name === "Safari") {
if (myappinstalledflag) {
location.href = url;
success = true;
} else {
success = false;
}
callback(success);
}
}
The Safari extension was easy to implement. It consisted of a single line of injection script:
myinject.js:
window.postMessage("myappinstalled", window.location.origin);
Then in the web page JavaScript, you need to first register the message event and set a flag if the message is received:
window.addEventListener('message', function (msg) {
if (msg.data === "myappinstalled") {
myappinstalledflag = true;
}
}, false);
This assumes the application which is associated with the custom protocol will manage the installation of the Safari extension.
In all cases, if the callback returns false, you know to inform the user that the application (i.e., it's custom protocol) is not installed.
You say you need to detect the browser's protocol handlers - do you really?
What if you did something like what happens when you download a file from sourceforge? Let's say you want to open myapp://something. Instead of simply creating a link to it, create a link to another HTML page accessed via HTTP. Then, on that page, say that you're attempting to open the application for them. If it doesn't work, they need to install your application, which they can do by clicking on the link you'll provide. If it does work, then you're all set.
This was a recommended approach for IE by Microsoft support
http://msdn.microsoft.com/en-us/library/ms537503%28VS.85%29.aspx#related_topics
"If you have some control over the binaries being installed on a user’s machine, checking the UA in script seems like a relevant approach:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\5.0\User Agent\Post Platform
" -- By M$ support
Every web page has access to the userAgent string and if you drop a custom post platform value, detecting this in javascript using navigator.userAgent is quite simple.
Fortunately, other major browsers like Firefox and Chrome (barring Safari :( ), do not throw "page not found" errors when a link with a custom protocol is clicked and the protocol is not installed on the users machine. IE is very unforgiving here, any trick to click in a invisible frame or trap javascript errors does not work and ends up with ugly "webpage cannot be displayed" error. The trick we use in our case is to inform users with browser specific images that clicking on the custom protocol link will open an application. And if they do not find the app opening up, they can click on an "install" page. In terms of XD this wprks way better than the ActiveX approach for IE.
For FF and Chrome, just go ahead and launch the custom protocol without any detection. Let the user tell you what he sees.
For Safari, :( no answers yet
I'm trying to do something similar and I just discovered a trick that works with Firefox. If you combine it with the trick for IE you can have one that works on both main browsers (I'm not sure if it works in Safari and I know it doesn't work in Chrome)
if (navigator.appName=="Microsoft Internet Explorer" && document.getElementById("testprotocollink").protocolLong=="Unknown Protocol") {
alert("No handler registered");
} else {
try {
window.location = "custom://stuff";
} catch(err) {
if (err.toString().search("NS_ERROR_UNKNOWN_PROTOCOL") != -1) {
alert("No handler registered");
}
}
}
In order for this to work you also need to have a hidden link somewhere on the page, like this:
<a id="testprotocollink" href="custom://testprotocol" style="display: none;">testprotocollink</a>
It's a bit hacky but it works. The Firefox version unfortunately still pops up the default alert that comes up when you try to visit a link with an unknown protocol, but it will run your code after the alert is dismissed.
You can try something like this:
function OpenCustomLink(link) {
var w = window.open(link, 'xyz', 'status=0,toolbar=0,menubar=0,height=0,width=0,top=-10,left=-10');
if(w == null) {
//Work Fine
}
else {
w.close();
if (confirm('You Need a Custom Program. Do you want to install?')) {
window.location = 'SetupCustomProtocol.exe'; //URL for installer
}
}
}
This is not a trivial task; one option might be to use signed code, which you could leverage to access the registry and/or filesystem (please note that this is a very expensive option). There is also no unified API or specification for code signing, so you would be required to generate specific code for each target browser. A support nightmare.
Also, I know that Steam, the gaming content delivery system, doesn't seem to have this problem solved either.
Here's another hacky answer that would require (hopefully light) modification to your application to 'phone home' on launch.
User clicks link, which attempts to launch the application. A unique
identifier is put in the link, so that it's passed to the
application when it launches. Web app shows a spinner or something of that nature.
Web page then starts checking for a
'application phone home' event from an app with this same unique ID.
When launched, your application does an HTTP post to your web app
with the unique identifier, to indicate presence.
Either the web page sees that the application launched, eventually, or moves on with a 'please download' page.

Categories