KLAC logo KLAC Word puzzle Available for

Dynamic script inject in Apache Cordova html app

If you ever want to load external javascript in your mobile web app, don’t suppose it is a straightforward task as in common web pages. Because the code comes from outside the app package, in technical terms, it is a code inject. And it brings several security issues you should not take easily.

That said, a mobile app built with frameworks like Apache Cordova can auto-update itself, replacing old code with a new one. Once implemented, it’s very effective because it makes the updating process faster, avoiding long times required for a regular update via the official store.

On startup the app can download the new content, store it in the reserved app storage, and load it dynamically.

Our game Aedo Episodes leverages the cordova File plugin and uses the cdvfile protocol to easily reference the downloaded code.

Here’s a function to inject the javascript into the app:

1
function includeScript(path, cb) {
2
    var node = document.createElement("script"), 
3
        okHandler,
4
        errHandler;
5
        
6
    node.src = path;
7
8
    okHandler = function () {
9
        this.removeEventListener("load", okHandler);
10
        this.removeEventListener("error", errHandler);
11
        cb();
12
    };
13
    errHandler = function (error) {
14
        this.removeEventListener("load", okHandler);
15
        this.removeEventListener("error", errHandler);
16
        cb("Error loading script: " + path);
17
    };
18
19
    node.addEventListener("load", okHandler);
20
    node.addEventListener("error", errHandler);
21
22
    document.body.appendChild(node);
23
}

A sample use, when the javascript is previously saved to the persistent storage.

1
includeScript("cdvfile://localhost/persistent/app/code.js", function () {
2
    //here the injection is completed, you can go next
3
    game.start();
4
});

Of course you can point to external javascript via http (i.e. jQuery in a CDN), but you should avoid it in a packaged app, for obvious reasons.

Windows Phone doesn’t allow script inject

This technique works well in all platforms except on Windows and Windows Phone. On these platforms security validation is stricter, so injecting code from untrusted source can’t be achieved in this way.

The provided winstore-jscompat.js isn’t a solution as it works well with dynamic HTML and CSS content, but fails with dynamic javascript, alias our external code. It set all script tags type to application/inert-* to avoid the code to run. Patching the lib by removing the set type lines, doesn’t work either, the webview excludes dynamic code from execution.

The solution is to inject the code directly, by manually creating a script tag and set its content, that is, the plain javascript code. You can either download or read it from the storage.

Microsoft requires developers are full aware of this technique impacts and forces the use of a specific API, the execUnsafeLocalFunction.

Here’s a snippet that injects code red from the storage through the file plugin.

1
function injectScriptWindows(scriptAsString) {
2
    MSApp.execUnsafeLocalFunction(function () {
3
        var node = document.createElement("script");
4
        node.text = scriptAsString; //here the code
5
        document.body.appendChild(node);
6
    });
7
}

To read the code from the persistent storage, the File API comes nicely into the picture.

1
function readLocalFile(path, cb) {
2
    window.requestFileSystem(window.PERSISTENT, 0, function (fs) {
3
        fs.root.getFile(path, {}, function (fileEntry) {
4
            fileEntry.file(function (file) {
5
                var reader = new FileReader();
6
7
                reader.onloadend = function (e) {
8
                    if (this.error) {
9
                        cb(this.error);
10
                    } else {
11
                        cb(null, this.result);
12
                    }
13
                };
14
15
                reader.readAsText(file);
16
            }, cb);
17
        }, cb);
18
    }, cb);
19
}

We can combine these functions to get something working on all platforms.

1
function includeScript(path, cb) {
2
    if (!(window.MSApp && window.MSApp.execUnsafeLocalFunction)) {
3
        var node = document.createElement("script"),
4
            okHandler, errHandler;
5
6
        node.src = path;
7
8
        okHandler = function () {
9
            this.removeEventListener("load", okHandler);
10
            this.removeEventListener("error", errHandler);
11
            cb();
12
        };
13
        errHandler = function (error) {
14
            this.removeEventListener("load", okHandler);
15
            this.removeEventListener("error", errHandler);
16
            cb("Error loading script: " + path);
17
        };
18
19
        node.addEventListener("load", okHandler);
20
        node.addEventListener("error", errHandler);
21
22
        document.body.appendChild(node);
23
    } else {
24
        readLocalFile(path, function (err, result) {
25
            MSApp.execUnsafeLocalFunction(function () {
26
                var node = document.createElement("script");
27
                node.text = result;
28
                document.body.appendChild(node);
29
                cb();
30
            });
31
        });
32
    }
33
}

Now, with this function you can inject code into html mobile apps, including iOS, Android and Windows Phone.

You can use it into a full auto update process. Stores allow this, so don’t worry about certification issues.

For example, you can download all content (HTML, CSS, JavaScript) to the persistent storage cdvfile://localhost/persistent/app and boot the app from there. Of course this is a simplified process. In a real world app, you should take into account many other factors like fails, partial downloads, file integrity, validation, version swap, you name it…

This website uses cookies to improve your experience