EddyVerbruggen / Custom-URL-scheme

:link: Launch your Cordova/PhoneGap app by a Custom URL scheme like mycoolapp://
1.03k stars 367 forks source link

RFC: Improving Windows Phone / UWP support #206

Open slorber opened 7 years ago

slorber commented 7 years ago

Hi,

I'm building a clipping workflow for a UWP Windows10 app (works on desktop and phone). I've documented the workflow here: https://github.com/slorber/cordova-windows-share-target

The share view loaded during the share operation redirects to the main app by using a procotol launch request.

Some important things noted that are worth discussing:

App started by protocol launch: handleOpenURL may not be fired

If the main app is not running and we do a protocol launch, then it's worth mentionning that the window.handleOpenURL is called BEFORE the cordova deviceready event. Therefor the user must be sure to have setup the callback before deviceready, otherwise he won't be able to intercept the protocol URI

Maybe it's a Cordova bug, maybe not. Hope to get a response in: https://issues.apache.org/jira/browse/CB-11924

In the meantime, the following code could be reworked so that, in case handleOpenUrl is not setup when activation event fires, we can "stage" the protocol request url and publish it later when the callback is setup, for example by listening to deviceready event?

(function () {
    function activatedHandler(e) {
        if (typeof handleOpenURL === "function" && e.uri) {
            handleOpenURL(e.uri.rawUri);
        }
    };

    document.addEventListener("activated", activatedHandler, false);
}());

could become something like:

(function () {

    var stagedUrl;

    function activatedHandler(e) {
        if (typeof handleOpenURL === "function" && e.uri) {
            handleOpenURL(e.uri.rawUri);
        }
       else if (e.uri) {
           stagedUrl = e.uri.rawUri;
       }
    };

    function devicereadyHandler(e) {
        if (stagedUrl  && typeof handleOpenURL) {
            handleOpenURL(e.uri.rawUri);
        }
    };

    document.addEventListener("activated", activatedHandler, false);
    document.addEventListener("deviceready", devicereadyHandler, false);
}());

This way, if the user actually setup the window.handleOpenURL callback on deviceready callback (which is likely), then he would be able to have his callback be run

Protocol launch manifest declaration may not declare a StartPage

Currently, the manifest declaration provided by this plugin is:

                <uap:Extension Category="windows.protocol" StartPage="www/index.html">
                    <uap:Protocol Name="stample" />
                </uap:Extension>

This may seem weird, but actually, when we launch the app (so it is on index.html), and then do protocol launch with this declaration (like I'm doing from the sharing view), instead of resuming the main app, it actually reloads the index.html (while I'm sure it was already on index.html)

Note that if I remove the StartPage entry, this time, on protocol launch, the app resumes instead of reloading index.html.

I'd recommend not declaring that StartPage, unless there's a good reason to.

Not idiomatic implementation

Not sure about this one, but it looks to me, according to Windows doc, that the implementation should rather look ike this:

function activationHandler(args) {
        if (args.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.protocol) {
            var url = args.detail.uri.rawUri.
            ... Do something with the url
        }
};
WinJS.Application.addEventListener("activated", activationHandler, false);

Is it unsafe to use WinJS directly in this plugin?

Edit: according to my tests, it looks like the WinJS version works better for initial event. Don't know why but I was able to make handleOpenURL work on initial event but now can't make it work anymore on my app.

slorber commented 7 years ago

cc Windows contributors: @EionRobb @rs22 @nadyaA @msegers

EionRobb commented 7 years ago

This way, if the user actually setup the window.handleOpenURL callback on deviceready callback (which is likely), then he would be able to have his callback be run

Kind of responded to in the next statement, but would it be better to save this to window.sessionStorage in the case of multi-page apps to be able to load if the page needs to change to process this, or would you leave it up to that app to handle the handleOpenURL callback appropriately?

I'd recommend not declaring that StartPage, unless there's a good reason to.

I was worried that this wouldn't build for Win8.0 (with VS2012) but it looks like it works, and would work a lot better for multi-page apps. Nice find :)

Is it unsafe to use WinJS directly in this plugin?

I wouldn't say it's "unsafe" but I would say it's unnecessary. The WinJS call WinJS.Application.addEventListener() under the hood translates to Windows.UI.WebUI.WebUIApplication.addEventListener() so you can use that without needing to load in all of WinJS. I believe the main cordova project needlessly includes WinJS, however we've removed it in our app with many benefits.

slorber commented 7 years ago

I have never build a multi-page app so I can't really speak about that.

Currently, as a workaroud in my app, I have the following JS code that is executed as soon as possible:

window.handleOpenURL = function(url) {
    window.handleOpenURLStaged = url;
    console.debug("window.handleOpenURLStaged = "+url);
}
if ( typeof WinJS !== "undefined" ) {
    function activationHandler(args) {
        if (args.detail.kind === Windows.ApplicationModel.Activation.ActivationKind.protocol) {
            var url = args.detail.uri.rawUri;
            window.handleOpenURLStaged = url;
            console.debug("window.handleOpenURLStaged (WinJS) = "+url);        
        }
    };
    WinJS.Application.addEventListener("activated", activationHandler, false);
}

I have these 2 statements in index.js, and my index.html basically looks like:

<html>
<head>...</head>
<body>
<div id="reactAppContainer" style="display: none"></div>
<link rel="stylesheet" type="text/css" href="stampleApp/app.css" media="screen"/>
<link rel="stylesheet" type="text/css" href="stampleApp/appPrint.css" media="print"/>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src="stampleApp/appPolyfills.js"></script>
<script type="text/javascript" src="stampleApp/appLibs.js"></script>
<script type="text/javascript" src="stampleApp/app.js"></script>
<script type="text/javascript" src="index.js"></script>
</body>
</html>

What I can see is that in my app, the handleOpenURL is not fired, while the WinJS based version actually is. I am a total Windows platform noob, so I actually don't really understand all implications here. I don't actually use WinJS in my app, so maybe it would be a better idea to run without it and simply use: Windows.UI.WebUI.WebUIApplication.addEventListener() instead? Just followed code samples of the Windows doc actually.

But as far as I understand, cordova windows platform automatically injects WinJS in index.html as of 4.5.0. cc @daserge @jicongw @vladimir-kotikov @alsorokin : What is the most efficient way to listen for a protocol launch? Should we use WinJS addEventListener?

About the startPage, I don't really know if a multipage app will stay on the page if there's a protocol launch. What I can say is that if I'm on index.html, and there's no startpage, it stays on index.html and does not reload the page. Worth noting that my app was previously using html5 url-based routing, and if the url has be changed (by replaceState/pushState), then doing the protocol request would actually always reload the index.html page. I had to rely on an in-memory version of historyjs to use app routing, and always stay on index.html page