From 9c5ad795e14e206321965fb4623d836280f444c4 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 1 Jun 2018 19:23:30 +0200 Subject: [PATCH] Service worker test --- www/common/outer/webworker.js | 63 ++++++++++++++ www/common/outer/worker-channel.js | 131 +++++++++++++++++++++++++++++ www/worker/inner.js | 46 +++++++++- www/worker/sw.js | 64 ++++++++++++++ www/worker2 | 1 + 5 files changed, 304 insertions(+), 1 deletion(-) create mode 100644 www/common/outer/webworker.js create mode 100644 www/common/outer/worker-channel.js create mode 100644 www/worker/sw.js create mode 120000 www/worker2 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('
sw.js ready'); + postMessage(["Hello worker"]); + return; + } + $container.append('
'); + $container.append(e.data); + }); + postMessage("INIT"); + }).catch(function(error) { + console.log('Registration failed with ' + error); + $container.append('Registration error: ' + error); + }); + } else { + console.log('NO SERVICE WORKER'); + } + $container.append('
inner.js ready'); }); }); diff --git a/www/worker/sw.js b/www/worker/sw.js new file mode 100644 index 000000000..7f102e49f --- /dev/null +++ b/www/worker/sw.js @@ -0,0 +1,64 @@ +var id = Math.floor(Math.random()*100000); + +var postMsg = function (client, data) { + client.postMessage(data); +}; + +var broadcast = function (data, excludes) { + // Loop over all available clients + clients.matchAll().then(function (clients) { + clients.forEach(function (client) { + if (excludes.indexOf(client.id) === -1) { + postMsg(client, data); + } + }) + }) +}; +var sendTo = function (data, clientId){ + clients.matchAll().then(function (clients) { + clients.some(function (client) { + if (client.id === clientId) { + postMsg(client, data) + } + }) + }) +}; +var getClients = function () { + clients.matchAll().then(function (clients) { + var cl = clients.map(function (c) { + console.log(JSON.stringify(c)); + return c.id; + }); + console.log(cl); + }); +}; + + + +self.addEventListener('message', function (e) { + console.log(clients); + console.log('worker received'); + console.log(e.data); + console.log(e.source); + var cId = e.source.id; + if (e.data === "INIT") { + broadcast(cId + ' has joined!', [cId]); + postMsg(e.source, {state: 'READY'}); + postMsg(e.source, "Welcome to SW " + id + "!"); + postMsg(e.source, "You are identified as " + cId); + } else { + console.log(e.data); + postMsg(e.source, 'Yo (Re: '+e.data+')'); + } +}); +self.addEventListener('install', function (e) { + console.log(e); + console.log('V1 installing…'); + self.skipWaiting(); +}); + +self.addEventListener('activate', function (e) { + console.log(e); + console.log('V1 now ready to handle fetches!'); +}); + diff --git a/www/worker2 b/www/worker2 new file mode 120000 index 000000000..b2b186dda --- /dev/null +++ b/www/worker2 @@ -0,0 +1 @@ +worker/ \ No newline at end of file