Ric
Ric

Reputation: 211

Chrome Packaged App with SQLite?

I was trying to integrate sql.js(JS based SQLite https://github.com/kripken/sql.js/) into my chrome app but as I launch my app, console shows the following errors:

Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "default-src 'self' chrome-extension-resource:". Note that 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "default-src 'self' chrome-extension-resource:".

My manifest file looks like this:

{
  "manifest_version": 2,
  "name": "Chrome App",        
  "description": "This is the test app!!!",
  "version": "1",
  "icons": {
    "128": "icon_128.png"
  },
  "permissions": ["storage"],
  "app": {
    "background": {
      "scripts": ["background.js"]
    },
    "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
  },
  "minimum_chrome_version": "28"
}

Upvotes: 2

Views: 2091

Answers (2)

dbf
dbf

Reputation: 3453

@MarcRochkind I would like to add some knowledge to your book for integrating SQL.js in Chrome Apps.

It is very well possible with very little effort (considered the obedience of policies and rules).

In order to integrate anything that uses eval, you need to sandbox that particular part of the script. In case of SQL.js, it's the entire library.

This can be done with an iframe which needs to be set in the main .html document that's called for creating a (or the main) window, e.g. chrome.app.window.create('index-app.html', { ..

The base of communication between the main document and the iframe will be by using postMessage for sending and receiving messages.

Let's say the source of this iframe is called /iframes/sqljs-sandboxed.html. In the manifest.json you need to specify sqljs-sandboxed.html as a sandbox. A designated sandbox makes it possible to run eval and eval-like constructs like new Function.

{
  "manifest_version": 1,
  "name": "SQL.js Test",
  ..
  "sandbox": {
    "pages": [
      "iframes/sqljs-sandboxed.html",
    ]
  }
}

The sqljs-sandboxed.html uses an event listener to react on an event of type message. Here you can simply add logic (for simplicity sake I used a switch statement) to do anything structured with SQL.js.

The content of sqljs-sandboxed.html as an example:

<script src="/vendor/kripken/sql.js"></script>
<script>
  (function(window, undefined) {

    // a test database
    var db = new SQL.Database();
    // create a table with some test values
    sqlstr = "CREATE TABLE hello (a int, b char);";
    sqlstr += "INSERT INTO hello VALUES (0, 'hello');";
    sqlstr += "INSERT INTO hello VALUES (1, 'world');";
    // run the query without returning anything    
    db.run(sqlstr);

    // our event listener for message
    window.addEventListener('message', function(event) {
      var params = event.data.params,
          data = event.data.data,
          context = {};

        try {
          switch(params.cmd) {
            case '/do/hello':

              // process anything with sql.js
              var result = db.exec("SELECT * FROM hello");

              // set the response context
              context = {
                message: '/do/hello',
                hash: params.hash,
                response: result
              };

              // send a response to the source (parent document)
              event.source.postMessage(context, event.origin);

              // for simplicity, resend a response to see if event in 
              // 'index-app.html' gets triggered a second time (which it
              // shouldn't)
              setTimeout(function() {
                event.source.postMessage(context, event.origin);
              }, '1000');

              break;
          }
        } catch(err) {
          console.log(err);
        }
    });

  })(window);  
</script>

A test database is created only once and the event listener mirrors an API using a simple switch. This means in order to use SQL.js you need to write against an API. This might be at, first glance, a little uncomfortable but in plain sense the idea is equivalent when implementing a REST service, which is, in my opinion, very comfortable in the long run.

In order to send requests, the index-app.html is the initiator. It's important to point out that multiple requests can be made to the iframe asynchronously. To prevent cross-fire, a state parameter is send with each request in the form of an unique-identifier (in my example unique-ish). At the same time a listener is attached on the message event which filters out the desired response and triggers its designated callback, and if triggered, removes it from the event stack.

For a fast demo, an object is created which automates attachment and detachment of the message event. Ultimately the listen function should eventually filter on a specific string value, e.g. sandbox === 'sql.js' (not implemented in this example) in order to speed up the filter selection for the many message events that can take place when using multiple iframes that are sandboxed (e.g. handlebars.js for templating).

var sqlRequest = function(request, data, callback) {

  // generate unique message id
  var hash = Math.random().toString(36).substr(2),
      // you can id the iframe as wished
      content_window = document.getElementById('sqljs-sandbox').contentWindow,
      listen = function(event) {

        // attach data to the callback to be used later
        this.data = event.data;
        // filter the correct response     
        if(hash === this.data.hash) {
          // remove listener
          window.removeEventListener('message', listen, false);
          // execute callback
          callback.call(this);
        }
      };

  // add listener
  window.addEventListener('message', listen, false);
  // post a request to the sqljs iframe
  content_window.postMessage({
    params: {
      cmd: request,
      hash: hash
    },
    data: data
  }, '*');
};

// wait for readiness to catch the iframes element
document.addEventListener('DOMContentLoaded', function() {
  // faking sqljs-sandboxed.html to be ready with a timeout
  setTimeout(function() {
    new sqlRequest('/do/hello', {
      allthedata: 'you need to pass'
    }, function() {
      console.log('response from sql.js');
      console.log(this.data);
    });
  }, '1000');

});

For simplicity, I'm using a timeout to prevent that the request is being send before the iframe was loaded. From my experience, it's best practice to let the iframe post a message to it's parent document that the iframe is loaded, from here on you can start using SQL.js.

Finally, in index-app.html you specify the iframe

<iframe src="/iframes/sqljs-sandboxed.html" id="sqljs-sandbox"></iframe>

Where the content of index-app.html could be

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
</head>
<body>
  <iframe src="/iframes/sqljs-sandboxed.html" id="sqljs-sandbox"></iframe>
  <h1>Hello, let's code with SQL.js!</h1>
  <script src="/assets/js/sqljs-request.js"></script>
</body>
</html>

Upvotes: 3

Marc Rochkind
Marc Rochkind

Reputation: 3740

  1. "content_security_policy" is not a documented manifest property of Chrome Apps.

  2. To my knowledge, sql.js is not compatible with Chrome Apps, as your error message indicates.

  3. A variation of SQLite, Web SQL, is specifically documented as not working with Chrome Apps.

  4. IndexedDB does work with Chrome Apps, but (a) it's not SQL-based, and (b) it's of limited utility because it's sandboxed and data is not visible to other apps, not even other Chrome Apps.

  5. Your reference to "Chrome Packaged Apps" may mean that you're thinking of legacy "packaged apps," which operate under different rules than the newer Chrome Apps. However, packaged apps are no longer supported by Google and should not be developed. Perhaps you were looking at documentation or examples of package apps, not Chrome Apps.

Upvotes: 1

Related Questions