Wednesday, April 6, 2016

Knockout in Chrome Web Apps (Yes!)

In a previous post I lamented on the fact that you can't use Knockout when creating a Chrome web app. The problem is that Knockout doesn't play well with Chrome's content security policy (CSP) because its binding engine uses "new Function" to parse bindings. This could allow someone to inject malicious code into the Chrome app which has access to low level resources that normal HTML5 apps don't.

After looking around though I came upon a solution to the CSP problem in Knockout. It seems I wasn't the only one with the problem. I found that Brian Hunt created a binding provider for Knockout that is CSP compliant. The project, called Knockout Secure Binding (KSB) can be found on GitHub. It is a replacement for Knockout's built in binding provider.

KSB is not a complete replacement though. It is limited in the kind of bindings you can create. Since it can't use "new Function" to parse bindings you can't create more complex bindings such as passing parameters into functions, and only a subset of JS expressions are available. But these limitations can easily be resolved by being more mindful of your bindings.

For example, say you have a binding for a "like" button that sets a certain thing as being liked. It calls a function in your view model called "like" and passes in the thing to like and true or false.

<button data-bind="click: like("knockoutJS", true)">Like</button>
<button data-bind="click: like("knockoutJS", false)">Dislike</button>

This won't work when using KSB because it doesn't support passing parameters to functions. You will need to rewrite the binding so it doesn't have to pass parameters.

<button data-bind="click: likeKnockoutJS">Like</button>
<button data-bind="click: dislikeKnockoutJS">Dislike</button>

Now the expression is moved back into the view model into functions called likeKnockoutJS and dislikeKnockoutJS. The downside is that this is going to require your view model to have a lot more functions in it, but your bindings will probably be a lot cleaner in the end.

KSB supports most any logical expression including comparison and mathematical operators, so you can perform some basic binding expressions.

<button data-bind="visible: !knockoutJSLiked())">Like</button>
<button data-bind="visible: knockoutJSLikeCount() > 0">Dislike</button>

Here the like button is visible only when the knockoutJSLiked() function does not return true. The dislike button is only visible when the knockoutJSLikeCount() function returns a value greater than zero.

To use KSB you just need to tell Knockout to use it as your binding provider before applying your bindings. You create an instance of a ko.secureBindingsProvider passing in the options you need and set that as the ko.bindingProvider.

let options = {
    attribute: "data-bind",        // default "data-sbind"
    globals: window,               // default {}
    bindings: ko.bindingHandlers,  // default ko.bindingHandlers
    noVirtualElements: false       // default true
};
ko.bindingProvider.instance = new ko.secureBindingsProvider(options);
ko.applyBindings(this.viewModel);

For more info on using KSB see the GitHub page.

In my app (PowerChord) I had to do some reworking to get my bindings to work with KSB. I also had to rewrite some components to make them work, which was probably a good thing because they were a bit convoluted. KSB forced me to write my components the way they should have been written instead of trying to use shortcuts with complicated bindings in my view.

Overall I'm very pleased with KSB and highly recommend it if you are writing a Chrome web app or need to implement the CSP and want to use Knockout as your binding framework (which I highly recommend).

No comments:

Post a Comment