diff --git a/www/common/outer/webworker.js b/www/common/outer/webworker.js
new file mode 100644
index 000000000..81cd2fa9f
--- /dev/null
+++ b/www/common/outer/webworker.js
@@ -0,0 +1,63 @@
+/* jshint ignore:start */
+importScripts('/bower_components/requirejs/require.js');
+require.config({
+ // fix up locations so that relative urls work.
+ baseUrl: '/',
+ paths: {
+ // jquery declares itself as literally "jquery" so it cannot be pulled by path :(
+ "jquery": "/bower_components/jquery/dist/jquery.min",
+ // json.sortify same
+ "json.sortify": "/bower_components/json.sortify/dist/JSON.sortify",
+ cm: '/bower_components/codemirror'
+ },
+ map: {
+ '*': {
+ 'css': '/bower_components/require-css/css.js',
+ 'less': '/common/RequireLess.js',
+ }
+ }
+});
+
+window = self;
+localStorage = {
+ setItem: function (k, v) { localStorage[k] = v; },
+ getItem: function (k) { return localStorage[k]; }
+};
+require([
+ '/common/common-util.js',
+ '/common/outer/worker-channel.js',
+ '/common/outer/store-rpc.js'
+], function (Util, Channel, Rpc) {
+ var msgEv = Util.mkEvent();
+
+ Channel.create(msgEv, postMessage, function (chan) {
+ console.log('ww ready');
+ Object.keys(Rpc.queries).forEach(function (q) {
+ if (q === 'CONNECT') { return; }
+ chan.on(q, function (data, cb) {
+ try {
+ Rpc.queries[q](data, cb);
+ } catch (e) {
+ console.error('Error in webworker when executing query ' + q);
+ console.error(e);
+ console.log(data);
+ }
+ });
+ });
+ chan.on('CONNECT', function (cfg, cb) {
+ console.log('onConnect');
+ // load Store here, with cfg, and pass a "query" (chan.query)
+ cfg.query = function (cmd, data, cb) {
+ chan.query(cmd, data, function (err, data) {
+ if (err) { return void cb({error: err}); }
+ cb(data);
+ });
+ };
+ Rpc.queries['CONNECT'](cfg, cb);
+ });
+ }, true);
+
+ onmessage = function (e) {
+ msgEv.fire(e);
+ };
+});
diff --git a/www/common/outer/worker-channel.js b/www/common/outer/worker-channel.js
new file mode 100644
index 000000000..c93fc6308
--- /dev/null
+++ b/www/common/outer/worker-channel.js
@@ -0,0 +1,131 @@
+// This file provides the API for the channel for talking to and from the sandbox iframe.
+define([
+ //'/common/sframe-protocol.js',
+ '/common/common-util.js'
+], function (/*SFrameProtocol,*/ Util) {
+
+ var mkTxid = function () {
+ return Math.random().toString(16).replace('0.', '') + Math.random().toString(16).replace('0.', '');
+ };
+
+ var create = function (onMsg, postMsg, cb, isWorker) {
+ var evReady = Util.mkEvent(true);
+ var handlers = {};
+ var queries = {};
+
+ // list of handlers which are registered from the other side...
+ var insideHandlers = [];
+ var callWhenRegistered = {};
+
+ var chan = {};
+
+ // Send a query. channel.query('Q_SOMETHING', { args: "whatever" }, function (reply) { ... });
+ chan.query = function (q, content, cb) {
+ var txid = mkTxid();
+ var timeout = setTimeout(function () {
+ delete queries[txid];
+ console.log("Timeout making query " + q);
+ }, 30000);
+ queries[txid] = function (data, msg) {
+ clearTimeout(timeout);
+ delete queries[txid];
+ cb(undefined, data.content, msg);
+ };
+ evReady.reg(function () {
+ postMsg(JSON.stringify({
+ txid: txid,
+ content: content,
+ q: q
+ }));
+ });
+ };
+
+ // Fire an event. channel.event('EV_SOMETHING', { args: "whatever" });
+ var event = chan.event = function (e, content) {
+ evReady.reg(function () {
+ postMsg(JSON.stringify({ content: content, q: e }));
+ });
+ };
+
+ // Be notified on query or event. channel.on('EV_SOMETHING', function (args, reply) { ... });
+ // If the type is a query, your handler will be invoked with a reply function that takes
+ // one argument (the content to reply with).
+ chan.on = function (queryType, handler, quiet) {
+ (handlers[queryType] = handlers[queryType] || []).push(function (data, msg) {
+ handler(data.content, function (replyContent) {
+ postMsg(JSON.stringify({
+ txid: data.txid,
+ content: replyContent
+ }));
+ }, msg);
+ });
+ if (!quiet) {
+ event('EV_REGISTER_HANDLER', queryType);
+ }
+ };
+
+ // If a particular handler is registered, call the callback immediately, otherwise it will be called
+ // when that handler is first registered.
+ // channel.whenReg('Q_SOMETHING', function () { ...query Q_SOMETHING?... });
+ chan.whenReg = function (queryType, cb, always) {
+ var reg = always;
+ if (insideHandlers.indexOf(queryType) > -1) {
+ cb();
+ } else {
+ reg = true;
+ }
+ if (reg) {
+ (callWhenRegistered[queryType] = callWhenRegistered[queryType] || []).push(cb);
+ }
+ };
+
+ // Same as whenReg except it will invoke every time there is another registration, not just once.
+ chan.onReg = function (queryType, cb) { chan.whenReg(queryType, cb, true); };
+
+ chan.on('EV_REGISTER_HANDLER', function (content) {
+ if (callWhenRegistered[content]) {
+ callWhenRegistered[content].forEach(function (f) { f(); });
+ delete callWhenRegistered[content];
+ }
+ insideHandlers.push(content);
+ });
+ chan.whenReg('EV_REGISTER_HANDLER', evReady.fire);
+
+ // Make sure both iframes are ready
+ var isReady =false;
+ chan.onReady = function (h) {
+ if (isReady) {
+ return void h();
+ }
+ if (typeof(h) !== "function") { return; }
+ chan.on('EV_RPC_READY', function () { isReady = true; h(); });
+ };
+ chan.ready = function () {
+ chan.whenReg('EV_RPC_READY', function () {
+ chan.event('EV_RPC_READY');
+ });
+ };
+
+ onMsg.reg(function (msg) {
+ var data = JSON.parse(msg.data);
+ if (typeof(data.q) === 'string' && handlers[data.q]) {
+ handlers[data.q].forEach(function (f) {
+ f(data || JSON.parse(msg.data), msg);
+ data = undefined;
+ });
+ } else if (typeof(data.q) === 'undefined' && queries[data.txid]) {
+ queries[data.txid](data, msg);
+ } else {
+ console.log("DROP Unhandled message");
+ console.log(msg.data, isWorker);
+ console.log(msg);
+ }
+ });
+ if (isWorker) {
+ evReady.fire();
+ }
+ cb(chan);
+ };
+
+ return { create: create };
+});
diff --git a/www/worker/inner.js b/www/worker/inner.js
index 667f09221..6625960d5 100644
--- a/www/worker/inner.js
+++ b/www/worker/inner.js
@@ -51,6 +51,8 @@ define([
if (!window.Worker) {
return void $container.text("WebWorkers not supported by your browser");
}
+ /*
+ // Shared worker
console.log('ready');
var myWorker = new SharedWorker('/worker/worker.js');
console.log(myWorker);
@@ -65,7 +67,49 @@ define([
}
$container.append('
');
$container.append(e.data);
- };
+ };*/
+
+ // Service worker
+ if ('serviceWorker' in navigator) {
+ var postMessage = function (data) {
+ if (navigator.serviceWorker && navigator.serviceWorker.controller) {
+ navigator.serviceWorker.controller.postMessage(data);
+ }
+ };
+ console.log('here');
+ navigator.serviceWorker.register('/worker/sw.js', {scope: '/'})
+ .then(function(reg) {
+ console.log(reg);
+ console.log('Registration succeeded. Scope is ' + reg.scope);
+ $container.append('
');
+ $container.append('Registered! (scope: ' + reg.scope +')');
+ reg.onupdatefound = function () {
+ console.log('new SW version found!');
+ // KILL EVERYTHING
+ UI.confirm("New version detected, you have to reload", function (yes) {
+ if (yes) { common.gotoURL(); }
+ });
+ };
+ // Here we add the event listener for receiving messages
+ navigator.serviceWorker.addEventListener('message', function (e) {
+ var data = e.data;
+ if (data && data.state === "READY") {
+ $container.append('