hampusohlsson / browser-deeplink

Redirect mobile website users to your native iOS and/or Android app
MIT License
493 stars 173 forks source link

Does it work in iOS 8? #6

Open godspeedelbow opened 9 years ago

godspeedelbow commented 9 years ago

Hey Hampus,

I have been having mixed results on Android (it doesn't work initially, but only when you click back - I use chrome 40).

This article http://www.tuaw.com/2014/06/18/ios-8-safari-can-block-ads-from-automatically-redirecting-to-the/ explains that it would pretty much impossible on latest iOS, or am I mistaken?

godspeedelbow commented 9 years ago

Just realized that this would only break fallback behavior: opening the app store. Perhaps that's not such a good choice as default behavior in that case

hampusohlsson commented 9 years ago

Right. On another note, I was thinking that the plugin API could be re-structured a little. Perhaps it would be useful to be able to check whether the user has the app installed or not. Which takes a callback which returns whether it was successful or not. Would love your input on this.

On the client side you could call:

deeplink.canOpen("myapp://xyz", myCustomCallback);

or just

deeplink.open("myapp://xyz");

Haven't tested this code, but I imagine it could look something like this:

// Parse android URL
var parseUri = function(uri) {
    if (isAndroid() && !navigator.userAgent.match(/Firefox/)) {
        var matches = uri.match(/([^:]+):\/\/(.+)$/i);
        uri = "intent://" + matches[2] + "#Intent;scheme=" + matches[1];
        uri += ";package=" + settings.android.appId + ";end";
    }
    return uri;
}

// Check if device can open a certian link
var canOpen = function(uri, callback) {
    if (!isMobile()) {
        callback(new Error("Not on mobile"), false);
    }

    var ts = Date.now();
    var timeout = setTimeout(function() {
        var wait = settings.delay + settings.delta;
        if ((Date.now() - ts) < wait) {
            callback(new Error("Could not open app"), false);
        }
    }, settings.delay);

    var iframe = document.createElement("iframe");
    iframe.onload = function() {
        clearTimeout(timeout);
        iframe.parentNode.removeChild(iframe);
        callback(null, true);
    };

    iframe.src = parseUri(uri);
    iframe.setAttribute("style", "display:none;");
    document.body.appendChild(iframe);
}

// Open app or fallback
var open = function(uri) {
    canOpen(uri, function(err, success) {
        if (!err || settings.fallback === "openAppStore") {
            window.location.href = parseUri(uri);
        } else if (typeof settings.fallback === "function") {
            settings.fallback(err, success);
        }
    });
}
hampusohlsson commented 9 years ago

No idea if this would work with the Android intent thing though

godspeedelbow commented 9 years ago

Hey, that's a great add-on, I like it!

I would say that the callback of canOpen doesn't have to be called with both an error and success because they're mutually exclusive: I'd stick with only the error param. Then in open you can just check that argument for truthy.

Another thing I just thought about, what if the iframe has a delay with opening (because of some external unexpected reason) and it loads after the timeout function ran, the callback gets called twice right? Maybe we should add to the setTimeout function, before var wait = ..., timeout = null; to tell us that the timeout has happened. Then in the iframe.onload function we add before clearTimeout(timeout), if (!timeout) return; Now, the callback will never be called twice. [ it's a bit unhandy that comments don't have line numbers :) ]

In general, I wouldn't know if it works on Android, because on Chrome 40 it doesn't work like it's intended (almost wrote intented there). Do you know of a different method than the iframe method?

I'm deploying your plugin tomorrow on our site to redirect users to our app! :)