From 6ab29f8f3aec11d970b2345000b98ddee3bdebe0 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 6 Jun 2018 15:58:43 +0200 Subject: [PATCH] Test service worker and shared worker store --- www/common/cryptpad-common.js | 298 +++++------- www/common/outer/async-store.js | 502 +++++++++++++------- www/common/outer/chainpad-netflux-worker.js | 13 +- www/common/outer/serviceworker.js | 167 +++++++ www/common/outer/sharedworker.js | 204 ++++++++ www/common/outer/store-rpc.js | 190 +------- www/common/outer/webworker.js | 59 ++- www/common/outer/worker-channel.js | 2 +- www/worker/inner.js | 32 +- www/worker/sw.js | 6 +- 10 files changed, 926 insertions(+), 547 deletions(-) create mode 100644 www/common/outer/serviceworker.js create mode 100644 www/common/outer/sharedworker.js diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 77707860c..fe104d5ec 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -691,103 +691,6 @@ define([ LOADING_DRIVE: common.loading.onDriveEvent.fire }; - /* - var onMessage = function (cmd, data, cb) { - cb = cb || function () {}; - if (queries[cmd]) { - return void queries[cmd](data, cb); - } else { - console.error("Unhandled command " + cmd); - } - /* - switch (cmd) { - case 'REQUEST_LOGIN': { - requestLogin(); - break; - } - case 'UPDATE_METADATA': { - common.changeMetadata(); - break; - } - case 'UPDATE_TOKEN': { - var localToken = tryParsing(localStorage.getItem(Constants.tokenKey)); - if (localToken !== data.token) { requestLogin(); } - break; - } - case 'Q_FRIEND_REQUEST': { - common.messaging.onFriendRequest.fire(data, cb); - break; - } - case 'EV_FRIEND_COMPLETE': { - common.messaging.onFriendComplete.fire(data); - break; - } - // Network - case 'NETWORK_DISCONNECT': { - common.onNetworkDisconnect.fire(); break; - } - case 'NETWORK_RECONNECT': { - common.onNetworkReconnect.fire(data); break; - } - // Messenger - case 'CONTACTS_MESSAGE': { - common.messenger.onMessageEvent.fire(data); break; - } - case 'CONTACTS_JOIN': { - common.messenger.onJoinEvent.fire(data); break; - } - case 'CONTACTS_LEAVE': { - common.messenger.onLeaveEvent.fire(data); break; - } - case 'CONTACTS_UPDATE': { - common.messenger.onUpdateEvent.fire(data); break; - } - case 'CONTACTS_FRIEND': { - common.messenger.onFriendEvent.fire(data); break; - } - case 'CONTACTS_UNFRIEND': { - common.messenger.onUnfriendEvent.fire(data); break; - } - // Pad - case 'PAD_READY': { - common.padRpc.onReadyEvent.fire(); break; - } - case 'PAD_MESSAGE': { - common.padRpc.onMessageEvent.fire(data); break; - } - case 'PAD_JOIN': { - common.padRpc.onJoinEvent.fire(data); break; - } - case 'PAD_LEAVE': { - common.padRpc.onLeaveEvent.fire(data); break; - } - case 'PAD_DISCONNECT': { - common.padRpc.onDisconnectEvent.fire(data); break; - } - case 'PAD_ERROR': { - common.padRpc.onErrorEvent.fire(data); break; - } - // Drive - case 'DRIVE_LOG': { - common.drive.onLog.fire(data); break; - } - case 'DRIVE_CHANGE': { - common.drive.onChange.fire(data); break; - } - case 'DRIVE_REMOVE': { - common.drive.onRemove.fire(data); break; - } - // Account deletion - case 'DELETE_ACCOUNT': { - common.startAccountDeletion(cb); break; - } - // Loading - case 'LOADING_DRIVE': { - common.loading.onDriveEvent.fire(data); break; - } - } - };*/ - common.ready = (function () { var env = {}; var initialized = false; @@ -849,98 +752,143 @@ define([ anonHash: LocalStore.getFSHash(), localToken: tryParsing(localStorage.getItem(Constants.tokenKey)), language: common.getLanguage(), - messenger: rdyCfg.messenger, - driveEvents: rdyCfg.driveEvents + messenger: rdyCfg.messenger, // Boolean + driveEvents: rdyCfg.driveEvents // Boolean }; if (sessionStorage[Constants.newPadPathKey]) { cfg.initialPath = sessionStorage[Constants.newPadPathKey]; delete sessionStorage[Constants.newPadPathKey]; } - /*AStore.query("CONNECT", cfg, waitFor(function (data) { - if (data.error) { throw new Error(data.error); } - if (data.state === 'ALREADY_INIT') { - data = data.returned; - } - if (data.anonHash && !cfg.userHash) { LocalStore.setFSHash(data.anonHash); } - - / *if (cfg.userHash && sessionStorage) { - // copy User_hash into sessionStorage because cross-domain iframes - // on safari replaces localStorage with sessionStorage or something - sessionStorage.setItem(Constants.userHashKey, cfg.userHash); - }* / - - if (cfg.userHash) { - var localToken = tryParsing(localStorage.getItem(Constants.tokenKey)); - if (localToken === null) { - // if that number hasn't been set to localStorage, do so. - localStorage.setItem(Constants.tokenKey, data[Constants.tokenKey]); - } - } - - initFeedback(data.feedback); - initialized = true; - }));*/ + var channelIsReady = waitFor(); var msgEv = Util.mkEvent(); - var worker = new Worker('/common/outer/webworker.js'); - worker.onmessage = function (ev) { - msgEv.fire(ev); - }; - var postMsg = function (data) { - worker.postMessage(data); - }; - Channel.create(msgEv, postMsg, waitFor(function (chan) { - console.log('outer ready'); - Object.keys(queries).forEach(function (q) { - chan.on(q, function (data, cb) { - try { - queries[q](data, cb); - } catch (e) { - console.error("Error in outer when executing query " + q); - console.error(e); - console.log(data); + var postMsg, worker; + Nthen(function (waitFor2) { + if (SharedWorker) { + worker = new SharedWorker('/common/outer/sharedworker.js'); + console.log(worker); + worker.port.onmessage = function (ev) { + if (ev.data === "SW_READY") { + return; } + msgEv.fire(ev); + }; + postMsg = function (data) { + worker.port.postMessage(data); + }; + postMsg('INIT'); + } else if (false && 'serviceWorker' in navigator) { + var initializing = true; + var stopWaiting = waitFor2(); // Call this function when we're ready + + postMsg = function (data) { + if (worker) { return void worker.postMessage(data); } + }; + + navigator.serviceWorker.register('/common/outer/serviceworker.js', {scope: '/'}) + .then(function(reg) { + // Add handler for receiving messages from the service worker + navigator.serviceWorker.addEventListener('message', function (ev) { + if (initializing && ev.data === "SW_READY") { + initializing = false; + } else { + msgEv.fire(ev); + } + }); + + // Initialize the worker + // If it is active (probably running in another tab), just post INIT + if (reg.active) { + worker = reg.active; + postMsg("INIT"); + } + // If it was not active, wait for the "activated" state and post INIT + reg.onupdatefound = function () { + if (initializing) { + var w = reg.installing; + var onStateChange = function () { + if (w.state === "activated") { + worker = w; + postMsg("INIT"); + w.removeEventListener("statechange", onStateChange); + } + }; + w.addEventListener('statechange', onStateChange); + return; + } + // XXX + // New version detected (from another tab): kill? + console.error('New version detected: ABORT?'); + }; + return void stopWaiting(); + }).catch(function(error) { + /**/console.log('Registration failed with ' + error); + }); + } else if (Worker) { + worker = new Worker('/common/outer/webworker.js'); + worker.onmessage = function (ev) { + msgEv.fire(ev); + }; + postMsg = function (data) { + worker.postMessage(data); + }; + } else { + // TODO fallback no webworker? + console.error('NO SW OR WW'); + } + }).nThen(function () { + Channel.create(msgEv, postMsg, function (chan) { + console.log('Outer ready'); + Object.keys(queries).forEach(function (q) { + chan.on(q, function (data, cb) { + try { + queries[q](data, cb); + } catch (e) { + console.error("Error in outer when executing query " + q); + console.error(e); + console.log(data); + } + }); }); - }); - - postMessage = function (cmd, data, cb) { - /*setTimeout(function () { - AStore.query(cmd, data, cb); - });*/ - chan.query(cmd, data, function (err, data) { - if (err) { return void cb ({error: err}); } - cb(data); - }); - }; - postMessage('CONNECT', cfg, waitFor(function (data) { - if (data.error) { throw new Error(data.error); } - if (data.state === 'ALREADY_INIT') { - data = data.returned; - } - - if (data.anonHash && !cfg.userHash) { LocalStore.setFSHash(data.anonHash); } - - /*if (cfg.userHash && sessionStorage) { - // copy User_hash into sessionStorage because cross-domain iframes - // on safari replaces localStorage with sessionStorage or something - sessionStorage.setItem(Constants.userHashKey, cfg.userHash); - }*/ - - if (cfg.userHash) { - var localToken = tryParsing(localStorage.getItem(Constants.tokenKey)); - if (localToken === null) { - // if that number hasn't been set to localStorage, do so. - localStorage.setItem(Constants.tokenKey, data[Constants.tokenKey]); + postMessage = function (cmd, data, cb) { + chan.query(cmd, data, function (err, data) { + if (err) { return void cb ({error: err}); } + cb(data); + }); + }; + + console.log('Posting CONNECT'); + postMessage('CONNECT', cfg, function (data) { + if (data.error) { throw new Error(data.error); } + if (data.state === 'ALREADY_INIT') { + data = data.returned; } - } - initFeedback(data.feedback); - initialized = true; - })); + if (data.anonHash && !cfg.userHash) { LocalStore.setFSHash(data.anonHash); } + + /*if (cfg.userHash && sessionStorage) { + // copy User_hash into sessionStorage because cross-domain iframes + // on safari replaces localStorage with sessionStorage or something + sessionStorage.setItem(Constants.userHashKey, cfg.userHash); + }*/ - }), false); + if (cfg.userHash) { + var localToken = tryParsing(localStorage.getItem(Constants.tokenKey)); + if (localToken === null) { + // if that number hasn't been set to localStorage, do so. + localStorage.setItem(Constants.tokenKey, data[Constants.tokenKey]); + } + } + + initFeedback(data.feedback); + initialized = true; + channelIsReady(); + }); + + }, false); + }); }).nThen(function (waitFor) { // Load the new pad when the hash has changed diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 664ad56af..f26eb50b4 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -24,6 +24,8 @@ define([ var Store = {}; var postMessage = function () {}; + var broadcast = function () {}; + var sendDriveEvent = function () {}; var storeHash; @@ -35,10 +37,10 @@ define([ }; - Store.get = function (key, cb) { + Store.get = function (clientId, key, cb) { cb(Util.find(store.proxy, key)); }; - Store.set = function (data, cb) { + Store.set = function (clientId, data, cb) { var path = data.key.slice(); var key = path.pop(); var obj = Util.find(store.proxy, path); @@ -48,6 +50,8 @@ define([ } else { obj[key] = data.value; } + console.log('broadcasting displayName'); + broadcast([clientId], "UPDATE_METADATA"); onSync(cb); }; @@ -131,7 +135,7 @@ define([ /////////////////////// RPC ////////////////////////////////////// ////////////////////////////////////////////////////////////////// - Store.pinPads = function (data, cb) { + Store.pinPads = function (clientId, data, cb) { if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } if (typeof(cb) !== 'function') { console.error('expected a callback'); @@ -143,7 +147,7 @@ define([ }); }; - Store.unpinPads = function (data, cb) { + Store.unpinPads = function (clientId, data, cb) { if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } store.rpc.unpin(data, function (e, hash) { @@ -154,7 +158,7 @@ define([ var account = {}; - Store.getPinnedUsage = function (data, cb) { + Store.getPinnedUsage = function (clientId, data, cb) { if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } store.rpc.getFileListSize(function (err, bytes) { @@ -166,7 +170,7 @@ define([ }; // Update for all users from accounts and return current user limits - Store.updatePinLimit = function (data, cb) { + Store.updatePinLimit = function (clientId, data, cb) { if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } store.rpc.updatePinLimits(function (e, limit, plan, note) { if (e) { return void cb({error: e}); } @@ -177,7 +181,7 @@ define([ }); }; // Get current user limits - Store.getPinLimit = function (data, cb) { + Store.getPinLimit = function (clientId, data, cb) { if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } var ALWAYS_REVALIDATE = true; @@ -195,14 +199,14 @@ define([ cb(account); }; - Store.clearOwnedChannel = function (data, cb) { + Store.clearOwnedChannel = function (clientId, data, cb) { if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } store.rpc.clearOwnedChannel(data, function (err) { cb({error:err}); }); }; - Store.removeOwnedChannel = function (data, cb) { + Store.removeOwnedChannel = function (clientId, data, cb) { if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } store.rpc.removeOwnedChannel(data, function (err) { cb({error:err}); @@ -230,7 +234,7 @@ define([ }); }; - Store.uploadComplete = function (data, cb) { + Store.uploadComplete = function (clientId, data, cb) { if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } if (data.owned) { // Owned file @@ -247,7 +251,7 @@ define([ }); }; - Store.uploadStatus = function (data, cb) { + Store.uploadStatus = function (clientId, data, cb) { if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } store.rpc.uploadStatus(data.size, function (err, res) { if (err) { return void cb({error:err}); } @@ -255,7 +259,7 @@ define([ }); }; - Store.uploadCancel = function (data, cb) { + Store.uploadCancel = function (clientId, data, cb) { if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } store.rpc.uploadCancel(data.size, function (err, res) { if (err) { return void cb({error:err}); } @@ -263,7 +267,7 @@ define([ }); }; - Store.uploadChunk = function (data, cb) { + Store.uploadChunk = function (clientId, data, cb) { store.rpc.send.unauthenticated('UPLOAD', data.chunk, function (e, msg) { cb({ error: e, @@ -272,14 +276,15 @@ define([ }); }; - Store.initRpc = function (data, cb) { + Store.initRpc = function (clientId, data, cb) { + if (store.rpc) { return void cb(account); } require(['/common/pinpad.js'], function (Pinpad) { Pinpad.create(store.network, store.proxy, function (e, call) { if (e) { return void cb({error: e}); } store.rpc = call; - Store.getPinLimit(null, function (obj) { + Store.getPinLimit(null, null, function (obj) { if (obj.error) { console.error(obj.error); } account.limit = obj.limit; account.plan = obj.plan; @@ -302,7 +307,7 @@ define([ ////////////////////////////////////////////////////////////////// ////////////////// ANON RPC ////////////////////////////////////// ////////////////////////////////////////////////////////////////// - Store.anonRpcMsg = function (data, cb) { + Store.anonRpcMsg = function (clientId, data, cb) { if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); } store.anon_rpc.send(data.msg, data.data, function (err, res) { if (err) { return void cb({error: err}); } @@ -310,7 +315,7 @@ define([ }); }; - Store.getFileSize = function (data, cb) { + Store.getFileSize = function (clientId, data, cb) { if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); } var channelId = Hash.hrefToHexChannelId(data.href, data.password); @@ -324,7 +329,7 @@ define([ }); }; - Store.isNewChannel = function (data, cb) { + Store.isNewChannel = function (clientId, data, cb) { if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); } var channelId = Hash.hrefToHexChannelId(data.href, data.password); store.anon_rpc.send("IS_NEW_CHANNEL", channelId, function (e, response) { @@ -339,7 +344,7 @@ define([ }); }; - Store.getMultipleFileSize = function (data, cb) { + Store.getMultipleFileSize = function (clientId, data, cb) { if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); } if (!Array.isArray(data.files)) { return void cb({error: 'INVALID_FILE_LIST'}); @@ -355,7 +360,7 @@ define([ }); }; - Store.getDeletedPads = function (data, cb) { + Store.getDeletedPads = function (clientId, data, cb) { if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); } var list = getCanonicalChannelList(true); if (!Array.isArray(list)) { @@ -372,7 +377,8 @@ define([ }); }; - Store.initAnonRpc = function (data, cb) { + Store.initAnonRpc = function (clientId, data, cb) { + if (store.anon_rpc) { return void cb(); } require([ '/common/rpc.js', ], function (Rpc) { @@ -389,7 +395,7 @@ define([ ////////////////////////////////////////////////////////////////// // Get the metadata for sframe-common-outer - Store.getMetadata = function (data, cb) { + Store.getMetadata = function (clientId, data, cb) { var disableThumbnails = Util.find(store.proxy, ['settings', 'general', 'disableThumbnails']); var metadata = { // "user" is shared with everybody via the userlist @@ -421,7 +427,7 @@ define([ }; }; - Store.addPad = function (data, cb) { + Store.addPad = function (clientId, data, cb) { if (!data.href) { return void cb({error:'NO_HREF'}); } var pad = makePad(data.href, data.title); if (data.owners) { pad.owners = data.owners; } @@ -432,6 +438,9 @@ define([ if (e) { return void cb({error: "Error while adding a template:"+ e}); } var path = data.path || ['root']; store.userObject.add(id, path); + sendDriveEvent('DRIVE_CHANGE', { + path: ['drive', UserObject.FILES_DATA] + }, clientId); onSync(cb); }); }; @@ -465,7 +474,7 @@ define([ ownedPads.forEach(function (c) { var w = waitFor(); sem.take(function (give) { - Store.removeOwnedChannel(c, give(function (obj) { + Store.removeOwnedChannel(null, c, give(function (obj) { if (obj && obj.error) { console.error(obj.error); } w(); })); @@ -473,11 +482,11 @@ define([ }); }; - Store.deleteAccount = function (data, cb) { + Store.deleteAccount = function (clientId, data, cb) { var edPublic = store.proxy.edPublic; // No password for drive var secret = Hash.getSecrets('drive', storeHash); - Store.anonRpcMsg({ + Store.anonRpcMsg(clientId, { msg: 'GET_METADATA', data: secret.channel }, function (data) { @@ -488,7 +497,7 @@ define([ nThen(function (waitFor) { var token = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); store.proxy[Constants.tokenKey] = token; - postMessage("DELETE_ACCOUNT", token, waitFor()); + postMessage(clientId, "DELETE_ACCOUNT", token, waitFor()); }).nThen(function (waitFor) { removeOwnedPads(waitFor); }).nThen(function (waitFor) { @@ -498,7 +507,7 @@ define([ })); }).nThen(function (waitFor) { // Delete Drive - Store.removeOwnedChannel(secret.channel, waitFor()); + Store.removeOwnedChannel(clientId, secret.channel, waitFor()); }).nThen(function () { store.network.disconnect(); cb({ @@ -537,7 +546,7 @@ define([ * - driveReadme * - driveReadmeTitle */ - Store.createReadme = function (data, cb) { + Store.createReadme = function (clientId, data, cb) { require(['/common/cryptget.js'], function (Crypt) { var hash = Hash.createRandomHash('pad'); Crypt.put(hash, data.driveReadme, function (e) { @@ -551,7 +560,7 @@ define([ channel: channel, title: data.driveReadmeTitle, }; - Store.addPad(fileData, cb); + Store.addPad(clientId, fileData, cb); }); }); }; @@ -562,7 +571,7 @@ define([ * data * - anonHash */ - Store.migrateAnonDrive = function (data, cb) { + Store.migrateAnonDrive = function (clientId, data, cb) { require(['/common/mergeDrive.js'], function (Merge) { var hash = data.anonHash; Merge.anonDriveIntoUser(store, hash, cb); @@ -573,6 +582,7 @@ define([ if (typeof attr === "string") { console.error('DEPRECATED: use setAttribute with an array, not a string'); return { + path: ['settings'], obj: store.proxy.settings, key: attr }; @@ -589,23 +599,28 @@ define([ obj = obj[el]; }); return { + path: ['settings'].concat(attr), obj: obj, key: attr[attr.length-1] }; }; // Set the display name (username) in the proxy - Store.setDisplayName = function (value, cb) { + Store.setDisplayName = function (clientId, value, cb) { store.proxy[Constants.displayNameKey] = value; + broadcast([clientId], "UPDATE_METADATA"); onSync(cb); }; // Reset the drive part of the userObject (from settings) - Store.resetDrive = function (data, cb) { + Store.resetDrive = function (clientId, data, cb) { nThen(function (waitFor) { removeOwnedPads(waitFor); }).nThen(function () { store.proxy.drive = store.fo.getStructure(); + sendDriveEvent('DRIVE_CHANGE', { + path: ['drive', 'filesData'] + }, clientId); onSync(cb); }); }; @@ -617,25 +632,28 @@ define([ * - attr (Array) * - value (String) */ - Store.setPadAttribute = function (data, cb) { + Store.setPadAttribute = function (clientId, data, cb) { store.userObject.setPadAttribute(data.href, data.attr, data.value, function () { + sendDriveEvent('DRIVE_CHANGE', { + path: ['drive', UserObject.FILES_DATA] + }, clientId); onSync(cb); }); }; - Store.getPadAttribute = function (data, cb) { + Store.getPadAttribute = function (clientId, data, cb) { store.userObject.getPadAttribute(data.href, data.attr, function (err, val) { if (err) { return void cb({error: err}); } cb(val); }); }; - Store.setAttribute = function (data, cb) { + Store.setAttribute = function (clientId, data, cb) { try { var object = getAttributeObject(data.attr); object.obj[object.key] = data.value; } catch (e) { return void cb({error: e}); } onSync(cb); }; - Store.getAttribute = function (data, cb) { + Store.getAttribute = function (clientId, data, cb) { var object; try { object = getAttributeObject(data.attr); @@ -644,12 +662,12 @@ define([ }; // Tags - Store.listAllTags = function (data, cb) { + Store.listAllTags = function (clientId, data, cb) { cb(store.userObject.getTagsList()); }; // Templates - Store.getTemplates = function (data, cb) { + Store.getTemplates = function (clientId, data, cb) { var templateFiles = store.userObject.getFiles(['template']); var res = []; templateFiles.forEach(function (f) { @@ -658,7 +676,7 @@ define([ }); cb(res); }; - Store.incrementTemplateUse = function (href) { + Store.incrementTemplateUse = function (clientId, href) { store.userObject.getPadAttribute(href, 'used', function (err, data) { // This is a not critical function, abort in case of error to make sure we won't // create any issue with the user object or the async store @@ -669,12 +687,15 @@ define([ }; // Pads - Store.moveToTrash = function (data, cb) { + Store.moveToTrash = function (clientId, data, cb) { var href = Hash.getRelativeHref(data.href); store.userObject.forget(href); + sendDriveEvent('DRIVE_CHANGE', { + path: ['drive', UserObject.FILES_DATA] + }, clientId); onSync(cb); }; - Store.setPadTitle = function (data, cb) { + Store.setPadTitle = function (clientId, data, cb) { var title = data.title; var href = data.href; var channel = data.channel; @@ -683,14 +704,16 @@ define([ if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); } + var channelData = Store.channels && Store.channels[channel]; + var owners; - if (Store.channel && Store.channel.wc && channel === Store.channel.wc.id) { - owners = Store.channel.data.owners || undefined; + if (channelData && channelData.wc && channel === channelData.wc.id) { + owners = channelData.data.owners || undefined; } var expire; - if (Store.channel && Store.channel.wc && channel === Store.channel.wc.id) { - expire = +Store.channel.data.expire || undefined; + if (channelData && channelData.wc && channel === channelData.wc.id) { + expire = +channelData.data.expire || undefined; } var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {}; @@ -757,7 +780,7 @@ define([ // Add the pad if it does not exist in our drive if (!contains) { - Store.addPad({ + Store.addPad(clientId, { href: href, channel: channel, title: title, @@ -767,12 +790,16 @@ define([ path: data.path }, cb); return; + } else { + sendDriveEvent('DRIVE_CHANGE', { + path: ['drive', UserObject.FILES_DATA] + }, clientId); } onSync(cb); }; // Filepicker app - Store.getSecureFilesList = function (query, cb) { + Store.getSecureFilesList = function (clientId, query, cb) { var list = {}; var hashes = []; var types = query.types; @@ -801,42 +828,42 @@ define([ }); cb(list); }; - Store.getPadData = function (id, cb) { + Store.getPadData = function (clientId, id, cb) { cb(store.userObject.getFileData(id)); }; // Messaging (manage friends from the userlist) - var getMessagingCfg = function () { + var getMessagingCfg = function (clientId) { return { proxy: store.proxy, realtime: store.realtime, network: store.network, updateMetadata: function () { - postMessage("UPDATE_METADATA"); + postMessage(clientId, "UPDATE_METADATA"); }, - pinPads: Store.pinPads, + pinPads: function (data, cb) { Store.pinPads(null, data, cb); }, friendComplete: function (data) { - postMessage("EV_FRIEND_COMPLETE", data); + postMessage(clientId, "EV_FRIEND_COMPLETE", data); }, friendRequest: function (data, cb) { - postMessage("Q_FRIEND_REQUEST", data, cb); + postMessage(clientId, "Q_FRIEND_REQUEST", data, cb); }, }; }; - Store.inviteFromUserlist = function (data, cb) { - var messagingCfg = getMessagingCfg(); + Store.inviteFromUserlist = function (clientId, data, cb) { + var messagingCfg = getMessagingCfg(clientId); Messaging.inviteFromUserlist(messagingCfg, data, cb); }; - Store.addDirectMessageHandlers = function (data) { - var messagingCfg = getMessagingCfg(); + Store.addDirectMessageHandlers = function (clientId, data, cb) { + var messagingCfg = getMessagingCfg(clientId); Messaging.addDirectMessageHandler(messagingCfg, data.href); }; // Messenger // Get hashes for the share button - Store.getStrongerHash = function (data, cb) { + Store.getStrongerHash = function (clientId, data, cb) { var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {}; // If we have a stronger version in drive, add it and add a redirect button @@ -922,49 +949,106 @@ define([ /////////////////////// PAD ////////////////////////////////////// ////////////////////////////////////////////////////////////////// - // TODO with sharedworker - // channel will be an object storing the webchannel associated to each browser tab - var channel = Store.channel = { - queue: [], - data: {} - }; - Store.joinPad = function (data, cb) { + var channels = Store.channels = {}; + + Store.joinPad = function (clientId, data, cb) { + var isNew = typeof channels[data.channel] === "undefined"; + var channel = channels[data.channel] = channels[data.channel] || { + queue: [], + data: {}, + clients: [], + bcast: function (cmd, data, notMe) { + channel.clients.forEach(function (cId) { + if (cId === notMe) { return; } + postMessage(cId, cmd, data); + }); + }, + history: [], + pushHistory: function (msg, isCp) { + if (isCp) { + channel.history.push('cp|' + msg); + var i, cpCount = 1; + for (i = channel.history.length - 2; i > 0; i--) { + if (/^cp\|/.test(channel.history[i])) { break; } + } + channel.history = channel.history.slice(i); + return; + } + channel.history.push(msg); + } + }; + if (channel.clients.indexOf(clientId) === -1) { + channel.clients.push(clientId); + } + + if (!isNew && channel.wc) { + channel.wc.members.forEach(function (m) { + postMessage(clientId, "PAD_JOIN", m); + }); + channel.history.forEach(function (msg) { + postMessage(clientId, "PAD_MESSAGE", { + msg: CpNfWorker.removeCp(msg), + user: channel.wc.myID, + validateKey: channel.data.validateKey + }); + }); + postMessage(clientId, "PAD_READY"); + cb({ + myID: channel.wc.myID, + id: channel.wc.id, + members: channel.wc.members + }); + + return; + } var conf = { onReady: function (padData) { channel.data = padData || {}; - postMessage("PAD_READY"); - }, // post EV_PAD_READY - onMessage: function (user, m, validateKey) { - postMessage("PAD_MESSAGE", { + postMessage(clientId, "PAD_READY"); + }, + onMessage: function (user, m, validateKey, isCp) { + channel.pushHistory(m, isCp); + channel.bcast("PAD_MESSAGE", { user: user, msg: m, validateKey: validateKey }); - }, // post EV_PAD_MESSAGE + }, onJoin: function (m) { - postMessage("PAD_JOIN", m); - }, // post EV_PAD_JOIN + channel.bcast("PAD_JOIN", m); + }, onLeave: function (m) { - postMessage("PAD_LEAVE", m); - }, // post EV_PAD_LEAVE + channel.bcast("PAD_LEAVE", m); + }, onDisconnect: function () { - postMessage("PAD_DISCONNECT"); - }, // post EV_PAD_DISCONNECT + channel.bcast("PAD_DISCONNECT"); + }, onError: function (err) { - postMessage("PAD_ERROR", err); - }, // post EV_PAD_ERROR + channel.bcast("PAD_ERROR", err); + delete channels[data.channel]; // TODO test? + }, channel: data.channel, validateKey: data.validateKey, owners: data.owners, password: data.password, expire: data.expire, network: store.network, - readOnly: data.readOnly, + //readOnly: data.readOnly, onConnect: function (wc, sendMessage) { - channel.sendMessage = sendMessage; + channel.sendMessage = function (msg, cId, cb) { + // Send to server + sendMessage(msg, cb); + // Broadcast to other tabs + channel.pushHistory(CpNfWorker.removeCp(msg), /^cp\|/.test(msg)); + channel.bcast("PAD_MESSAGE", { + user: wc.myID, + msg: CpNfWorker.removeCp(msg), + validateKey: channel.data.validateKey + }, cId); + }; channel.wc = wc; channel.queue.forEach(function (data) { - sendMessage(data.message); + channel.sendMessage(data.message, clientId); }); cb({ myID: wc.myID, @@ -975,13 +1059,23 @@ define([ }; CpNfWorker.start(conf); }; - Store.sendPadMsg = function (data, cb) { - if (!channel.wc) { channel.queue.push(data); } - channel.sendMessage(data, cb); + Store.sendPadMsg = function (clientId, data, cb) { + console.log('sendPadMsg ' + data.channel); + var msg = data.msg; + var channel = channels[data.channel]; + if (!channel) { + console.log('no channel...'); + return; } + if (!channel.wc) { + console.log('no WC, push to queue'); + channel.queue.push(msg); + return void cb(); + } + channel.sendMessage(msg, clientId, cb); }; // GET_FULL_HISTORY from sframe-common-outer - Store.getFullHistory = function (data, cb) { + Store.getFullHistory = function (clientId, data, cb) { var network = store.network; var hkn = network.historyKeeper; //var crypto = Crypto.createEncryptor(data.keys); @@ -1016,67 +1110,178 @@ define([ network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', data.channel, data.validateKey])); }; - // TODO with sharedworker - // when the tab is closed, leave the pad - // Drive - Store.userObjectCommand = function (cmdData, cb) { + Store.userObjectCommand = function (clientId, cmdData, cb) { if (!cmdData || !cmdData.cmd) { return; } var data = cmdData.data; + var cb2 = function (data2) { + var paths = data.paths || [data.path] || []; + paths = paths.concat(data.newPath || []); + paths.forEach(function (p) { + sendDriveEvent('DRIVE_CHANGE', { + //path: ['drive', UserObject.FILES_DATA] + path: ['drive'].concat(p) + }, clientId); + }); + cb(data2); + }; switch (cmdData.cmd) { case 'move': - store.userObject.move(data.paths, data.newPath, cb); break; + store.userObject.move(data.paths, data.newPath, cb2); break; case 'restore': - store.userObject.restore(data.path, cb); break; + store.userObject.restore(data.path, cb2); break; case 'addFolder': - store.userObject.addFolder(data.path, data.name, cb); break; + store.userObject.addFolder(data.path, data.name, cb2); break; case 'delete': - store.userObject.delete(data.paths, cb, data.nocheck, data.isOwnPadRemoved); break; + store.userObject.delete(data.paths, cb2, data.nocheck, data.isOwnPadRemoved); break; case 'emptyTrash': - store.userObject.emptyTrash(cb); break; + store.userObject.emptyTrash(cb2); break; case 'rename': - store.userObject.rename(data.path, data.newName, cb); break; + store.userObject.rename(data.path, data.newName, cb2); break; default: cb(); } }; + // Clients management + var driveEventClients = []; + var messengerEventClients = []; + + Store._removeClient = function (clientId) { + var driveIdx = driveEventClients.indexOf(clientId); + if (driveIdx !== -1) { + driveEventClients.splice(driveIdx, 1); + } + var messengerIdx = messengerEventClients.indexOf(clientId); + if (messengerIdx !== -1) { + messengerEventClients.splice(messengerIdx, 1); + } + Object.keys(Store.channels).forEach(function (chanId) { + var chanIdx = Store.channels[chanId].clients.indexOf(clientId); + if (chanIdx !== -1) { + Store.channels[chanId].clients.splice(chanIdx, 1); + } + }); + }; + + // Special events + + var driveEventInit = false; + sendDriveEvent = function (q, data, sender) { + console.log('driveevent '+q); + console.log(JSON.stringify(driveEventClients)); + driveEventClients.forEach(function (cId) { + if (cId === sender) { return; } + postMessage(cId, q, data); + }); + }; + Store._subscribeToDrive = function (clientId) { + if (driveEventClients.indexOf(clientId) === -1) { + driveEventClients.push(clientId); + } + if (!driveEventInit) { + store.proxy.on('change', [], function (o, n, p) { + sendDriveEvent('DRIVE_CHANGE', { + old: o, + new: n, + path: p + }); + }); + store.proxy.on('remove', [], function (o, p) { + sendDriveEvent(clientId, 'DRIVE_REMOVE', { + old: o, + path: p + }); + }); + driveEventInit = true; + } + }; + + var messengerEventInit = false; + var sendMessengerEvent = function (q, data) { + messengerEventClients.forEach(function (cId) { + postMessage(cId, q, data); + }); + }; + Store._subscribeToMessenger = function (clientId) { + if (messengerEventClients.indexOf(clientId) === -1) { + messengerEventClients.push(clientId); + } + if (!messengerEventInit) { + var messenger = store.messenger = Messenger.messenger(store); + messenger.on('message', function (message) { + sendMessengerEvent('CONTACTS_MESSAGE', message); + }); + messenger.on('join', function (curvePublic, channel) { + sendMessengerEvent('CONTACTS_JOIN', { + curvePublic: curvePublic, + channel: channel, + }); + }); + messenger.on('leave', function (curvePublic, channel) { + sendMessengerEvent('CONTACTS_LEAVE', { + curvePublic: curvePublic, + channel: channel, + }); + }); + messenger.on('update', function (info, curvePublic) { + sendMessengerEvent('CONTACTS_UPDATE', { + curvePublic: curvePublic, + info: info, + }); + }); + messenger.on('friend', function (curvePublic) { + sendMessengerEvent('CONTACTS_FRIEND', { + curvePublic: curvePublic, + }); + }); + messenger.on('unfriend', function (curvePublic) { + sendMessengerEvent('CONTACTS_UNFRIEND', { + curvePublic: curvePublic, + }); + }); + messengerEventInit = true; + } + }; + + ////////////////////////////////////////////////////////////////// /////////////////////// Init ///////////////////////////////////// ////////////////////////////////////////////////////////////////// - var onReady = function (returned, cb) { + var onReady = function (clientId, returned, cb) { var proxy = store.proxy; var userObject = store.userObject = UserObject.init(proxy.drive, { - pinPads: Store.pinPads, - unpinPads: Store.unpinPads, - removeOwnedChannel: Store.removeOwnedChannel, + pinPads: function (data, cb) { Store.pinPads(null, data, cb); }, + unpinPads: function (data, cb) { Store.unpinPads(null, data, cb); }, + removeOwnedChannel: function (data, cb) { Store.removeOwnedChannel(null, data, cb); }, edPublic: store.proxy.edPublic, loggedIn: store.loggedIn, log: function (msg) { - postMessage("DRIVE_LOG", msg); + // broadcast to all drive apps + sendDriveEvent("DRIVE_LOG", msg); } }); nThen(function (waitFor) { - postMessage('LOADING_DRIVE', { + postMessage(clientId, 'LOADING_DRIVE', { state: 2 }); userObject.migrate(waitFor()); }).nThen(function (waitFor) { Migrate(proxy, waitFor(), function (version, progress) { - postMessage('LOADING_DRIVE', { + postMessage(clientId, 'LOADING_DRIVE', { state: 2, progress: progress }); }); }).nThen(function () { - postMessage('LOADING_DRIVE', { + postMessage(clientId, 'LOADING_DRIVE', { state: 3 }); userObject.fixFiles(); var requestLogin = function () { - postMessage("REQUEST_LOGIN"); + broadcast([], "REQUEST_LOGIN"); }; if (store.loggedIn) { @@ -1121,26 +1326,26 @@ define([ proxy.on('change', [Constants.displayNameKey], function (o, n) { if (typeof(n) !== "string") { return; } - postMessage("UPDATE_METADATA"); + broadcast([], "UPDATE_METADATA"); }); proxy.on('change', ['profile'], function () { // Trigger userlist update when the avatar has changed - postMessage("UPDATE_METADATA"); + broadcast([], "UPDATE_METADATA"); }); proxy.on('change', ['friends'], function () { // Trigger userlist update when the friendlist has changed - postMessage("UPDATE_METADATA"); + broadcast([], "UPDATE_METADATA"); }); proxy.on('change', ['settings'], function () { - postMessage("UPDATE_METADATA"); + broadcast([], "UPDATE_METADATA"); }); proxy.on('change', [Constants.tokenKey], function () { - postMessage("UPDATE_TOKEN", { token: proxy[Constants.tokenKey] }); + broadcast([], "UPDATE_TOKEN", { token: proxy[Constants.tokenKey] }); }); }); }; - var connect = function (data, cb) { + var connect = function (clientId, data, cb) { var hash = data.userHash || data.anonHash || Hash.createRandomHash('drive'); storeHash = hash; if (!hash) { @@ -1180,9 +1385,9 @@ define([ && !drive['filesData']) { drive[Constants.oldStorageKey] = []; } - postMessage('LOADING_DRIVE', { state: 1 }); + postMessage(clientId, 'LOADING_DRIVE', { state: 1 }); // Drive already exist: return the existing drive, don't load data from legacy store - onReady(returned, cb); + onReady(clientId, returned, cb); }) .on('change', ['drive', 'migrate'], function () { var path = arguments[2]; @@ -1190,15 +1395,15 @@ define([ if (path[0] === 'drive' && path[1] === "migrate" && value === 1) { rt.network.disconnect(); rt.realtime.abort(); - postMessage('NETWORK_DISCONNECT'); + broadcast([], 'NETWORK_DISCONNECT'); } }); rt.proxy.on('disconnect', function () { - postMessage('NETWORK_DISCONNECT'); + broadcast([], 'NETWORK_DISCONNECT'); }); rt.proxy.on('reconnect', function (info) { - postMessage('NETWORK_RECONNECT', {myId: info.myId}); + broadcast([], 'NETWORK_RECONNECT', {myId: info.myId}); }); }; @@ -1213,7 +1418,7 @@ define([ * - requestLogin */ var initialized = false; - Store.init = function (data, callback) { + Store.init = function (clientId, data, callback) { if (initialized) { return void callback({ state: 'ALREADY_INIT', @@ -1221,72 +1426,21 @@ define([ }); } initialized = true; - postMessage = function (cmd, d, cb) { - setTimeout(function () { - data.query(cmd, d, cb); // TODO temporary, will be replaced by webworker channel - }); + postMessage = function (clientId, cmd, d, cb) { + data.query(clientId, cmd, d, cb); + }; + broadcast = function (excludes, cmd, d, cb) { + data.broadcast(excludes, cmd, d, cb); }; store.data = data; - connect(data, function (ret) { + connect(clientId, data, function (ret) { if (Object.keys(store.proxy).length === 1) { Feedback.send("FIRST_APP_USE", true); } store.returned = ret; callback(ret); - - // Send events whenever there is a change or a removal in the drive - if (data.driveEvents) { - store.proxy.on('change', [], function (o, n, p) { - postMessage('DRIVE_CHANGE', { - old: o, - new: n, - path: p - }); - }); - store.proxy.on('remove', [], function (o, p) { - postMessage('DRIVE_REMOVE', { - old: o, - path: p - }); - }); - } - - if (data.messenger) { - var messenger = store.messenger = Messenger.messenger(store); - messenger.on('message', function (message) { - postMessage('CONTACTS_MESSAGE', message); - }); - messenger.on('join', function (curvePublic, channel) { - postMessage('CONTACTS_JOIN', { - curvePublic: curvePublic, - channel: channel, - }); - }); - messenger.on('leave', function (curvePublic, channel) { - postMessage('CONTACTS_LEAVE', { - curvePublic: curvePublic, - channel: channel, - }); - }); - messenger.on('update', function (info, curvePublic) { - postMessage('CONTACTS_UPDATE', { - curvePublic: curvePublic, - info: info, - }); - }); - messenger.on('friend', function (curvePublic) { - postMessage('CONTACTS_FRIEND', { - curvePublic: curvePublic, - }); - }); - messenger.on('unfriend', function (curvePublic) { - postMessage('CONTACTS_UNFRIEND', { - curvePublic: curvePublic, - }); - }); - } }); }; diff --git a/www/common/outer/chainpad-netflux-worker.js b/www/common/outer/chainpad-netflux-worker.js index be0030ceb..d34fa4a3b 100644 --- a/www/common/outer/chainpad-netflux-worker.js +++ b/www/common/outer/chainpad-netflux-worker.js @@ -22,6 +22,10 @@ define([], function () { var unBencode = function (str) { return str.replace(/^\d+:/, ''); }; + var removeCp = function (str) { + return str.replace(/^cp\|([A-Za-z0-9+\/=]{0,20}\|)?/, ''); + }; + var start = function (conf) { var channel = conf.channel; var validateKey = conf.validateKey; @@ -72,7 +76,7 @@ define([], function () { // at the beginning of each message on the server. // We have to make sure our regex ignores this nonce using {0,20} (our IDs // should only be 8 characters long) - return msg.replace(/^cp\|([A-Za-z0-9+\/=]{0,20}\|)?/, ''); + return removeCp(msg); }; var msgOut = function (msg) { @@ -124,6 +128,8 @@ define([], function () { lastKnownHash = msg.slice(0,64); + + var isCp = /^cp\|/.test(msg); var message = msgIn(peer, msg); verbose(message); @@ -134,7 +140,7 @@ define([], function () { message = unBencode(message);//.slice(message.indexOf(':[') + 1); // pass the message into Chainpad - onMessage(peer, message, validateKey); + onMessage(peer, message, validateKey, isCp); //sframeChan.query('Q_RT_MESSAGE', message, function () { }); }; @@ -260,7 +266,8 @@ define([], function () { }; return { - start: start + start: start, + removeCp: removeCp /*function (config) { config.sframeChan.whenReg('EV_RT_READY', function () { start(config); diff --git a/www/common/outer/serviceworker.js b/www/common/outer/serviceworker.js new file mode 100644 index 000000000..d7660c311 --- /dev/null +++ b/www/common/outer/serviceworker.js @@ -0,0 +1,167 @@ +/* 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]; } +}; + +var postMsg = function (client, data) { + client.postMessage(data); +}; + + +var debug = function (msg) { console.log(msg); }; +// debug = function () {}; + +var init = function (client, cb) { + debug('SW INIT'); + + require([ + '/common/common-util.js', + '/common/outer/worker-channel.js', + '/common/outer/store-rpc.js' + ], function (Util, Channel, Rpc) { + debug('SW Required ressources loaded'); + var msgEv = Util.mkEvent(); + + var postToClient = function (data) { + postMsg(client, data); + }; + Channel.create(msgEv, postToClient, function (chan) { + debug('SW Channel created'); + + var clientId = client.id; + self.tabs[clientId].chan = chan; + Object.keys(Rpc.queries).forEach(function (q) { + if (q === 'CONNECT') { return; } + if (q === 'JOIN_PAD') { return; } + if (q === 'SEND_PAD_MSG') { return; } + chan.on(q, function (data, cb) { + try { + Rpc.queries[q](clientId, 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) { + debug('SW Connect callback'); + if (self.store) { + if (cfg.driveEvents) { + Rpc._subscribeToDrive(clientId); + } + if (cfg.messenger) { + Rpc._subscribeToMessenger(clientId); + } + return void cb(self.store); + } + + debug('Loading new async store'); + // One-time initialization (init async-store) + cfg.query = function (cId, cmd, data, cb) { + cb = cb || function () {}; + self.tabs[cId].chan.query(cmd, data, function (err, data2) { + if (err) { return void cb({error: err}); } + cb(data2); + }); + }; + cfg.broadcast = function (excludes, cmd, data, cb) { + cb = cb || function () {}; + Object.keys(self.tabs).forEach(function (cId) { + if (excludes.indexOf(cId) !== -1) { return; } + self.tabs[cId].chan.query(cmd, data, function (err, data2) { + if (err) { return void cb({error: err}); } + cb(data2); + }); + }); + }; + Rpc.queries['CONNECT'](clientId, cfg, function (data) { + if (cfg.driveEvents) { + Rpc._subscribeToDrive(clientId); + } + if (cfg.messenger) { + Rpc._subscribeToMessenger(clientId); + } + if (data && data.state === "ALREADY_INIT") { + return void cb(data.returned); + } + self.store = data; + cb(data); + }); + }); + chan.on('JOIN_PAD', function (data, cb) { + self.tabs[clientId].channelId = data.channel; + try { + Rpc.queries['JOIN_PAD'](clientId, data, cb); + } catch (e) { + console.error('Error in webworker when executing query JOIN_PAD'); + console.error(e); + console.log(data); + } + }); + chan.on('SEND_PAD_MSG', function (msg, cb) { + var data = { + msg: msg, + channel: self.tabs[clientId].channelId + }; + try { + Rpc.queries['SEND_PAD_MSG'](clientId, data, cb); + } catch (e) { + console.error('Error in webworker when executing query SEND_PAD_MSG'); + console.error(e); + console.log(data); + } + }); + cb(); + }, true); + + self.tabs[client.id].msgEv = msgEv; + }); +}; + +self.tabs = {}; +self.addEventListener('message', function (e) { + var cId = e.source.id; + if (e.data === "INIT") { + if (tabs[cId]) { return; } + tabs[cId] = { + client: e.source + }; + init(e.source, function () { + postMsg(e.source, 'SW_READY'); + }); + } else if (self.tabs[cId] && self.tabs[cId].msgEv) { + self.tabs[cId].msgEv.fire(e); + } +}); +self.addEventListener('install', function (e) { + debug('V1 installing…'); + self.skipWaiting(); +}); + +self.addEventListener('activate', function (e) { + debug('V1 now ready to handle fetches!'); +}); + + diff --git a/www/common/outer/sharedworker.js b/www/common/outer/sharedworker.js new file mode 100644 index 000000000..9d5aa1187 --- /dev/null +++ b/www/common/outer/sharedworker.js @@ -0,0 +1,204 @@ +console.log('SW!'); +/* 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]; } +}; + +self.tabs = {}; +var findTab = function (port) { + var tab; + Object.keys(self.tabs).some(function (id) { + if (self.tabs[id].port === port) { + tab = port; + return true; + } + }); + return tab; +}; + +var postMsg = function (client, data) { + client.port.postMessage(data); +}; + +var debug = function (msg) { console.log(msg); }; +// debug = function () {}; + +var init = function (client, cb) { + debug('SharedW INIT'); + + require([ + '/common/common-util.js', + '/common/outer/worker-channel.js', + '/common/outer/store-rpc.js' + ], function (Util, Channel, Rpc) { + debug('SharedW Required ressources loaded'); + var msgEv = Util.mkEvent(); + + var postToClient = function (data) { + postMsg(client, data); + }; + Channel.create(msgEv, postToClient, function (chan) { + debug('SharedW Channel created'); + + var clientId = client.id; + client.chan = chan; + Object.keys(Rpc.queries).forEach(function (q) { + if (q === 'CONNECT') { return; } + if (q === 'JOIN_PAD') { return; } + if (q === 'SEND_PAD_MSG') { return; } + chan.on(q, function (data, cb) { + try { + Rpc.queries[q](clientId, 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) { + debug('SharedW connecting to store...'); + if (self.store) { + debug('Store already exists!'); + if (cfg.driveEvents) { + Rpc._subscribeToDrive(clientId); + } + if (cfg.messenger) { + Rpc._subscribeToMessenger(clientId); + } + return void cb(self.store); + } + + debug('Loading new async store'); + // One-time initialization (init async-store) + cfg.query = function (cId, cmd, data, cb) { + cb = cb || function () {}; + self.tabs[cId].chan.query(cmd, data, function (err, data2) { + if (err) { return void cb({error: err}); } + cb(data2); + }); + }; + cfg.broadcast = function (excludes, cmd, data, cb) { + cb = cb || function () {}; + Object.keys(self.tabs).forEach(function (cId) { + if (excludes.indexOf(cId) !== -1) { return; } + self.tabs[cId].chan.query(cmd, data, function (err, data2) { + if (err) { return void cb({error: err}); } + cb(data2); + }); + }); + }; + Rpc.queries['CONNECT'](clientId, cfg, function (data) { + if (cfg.driveEvents) { + Rpc._subscribeToDrive(clientId); + } + if (cfg.messenger) { + Rpc._subscribeToMessenger(clientId); + } + if (data && data.state === "ALREADY_INIT") { + self.store = data.returned; + return void cb(data.returned); + } + self.store = data; + cb(data); + }); + }); + chan.on('JOIN_PAD', function (data, cb) { + client.channelId = data.channel; + try { + Rpc.queries['JOIN_PAD'](clientId, data, cb); + } catch (e) { + console.error('Error in webworker when executing query JOIN_PAD'); + console.error(e); + console.log(data); + } + }); + chan.on('SEND_PAD_MSG', function (msg, cb) { + var data = { + msg: msg, + channel: client.channelId + }; + try { + Rpc.queries['SEND_PAD_MSG'](clientId, data, cb); + } catch (e) { + console.error('Error in webworker when executing query SEND_PAD_MSG'); + console.error(e); + console.log(data); + } + }); + cb(); + }, true); + + client.msgEv = msgEv; + }); +}; + +onconnect = function(e) { + debug('New ShardWorker client'); + var port = e.ports[0]; + var cId = Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)) + var client = self.tabs[cId] = { + id: cId, + port: port + }; + + port.onmessage = function (e) { + if (e.data === "INIT") { + if (client.init) { return; } + client.init = true; + init(client, function () { + postMsg(client, 'SW_READY'); + }); + } else if (client && client.msgEv) { + client.msgEv.fire(e); + } + }; +}; + +self.tabs = {}; +self.addEventListener('message', function (e) { + var cId = e.source.id; + if (e.data === "INIT") { + if (tabs[cId]) { return; } + tabs[cId] = { + client: e.source + }; + init(e.source, function () { + postMsg(e.source, 'SW_READY'); + }); + } else if (self.tabs[cId] && self.tabs[cId].msgEv) { + self.tabs[cId].msgEv.fire(e); + } +}); +self.addEventListener('install', function (e) { + debug('V1 installing…'); + self.skipWaiting(); +}); + +self.addEventListener('activate', function (e) { + debug('V1 now ready to handle fetches!'); +}); + + + diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index 94a6eee85..f44f8d1e2 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -73,195 +73,17 @@ define([ Rpc.query = function (cmd, data, cb) { if (queries[cmd]) { - queries[cmd](data, cb); + queries[cmd]('0', data, cb); } else { console.error('UNHANDLED_STORE_RPC'); } - /* - switch (cmd) { - // READY - case 'CONNECT': { - Store.init(data, cb); break; - } - case 'DISCONNECT': { - Store.disconnect(data, cb); break; - } - case 'CREATE_README': { - Store.createReadme(data, cb); break; - } - case 'MIGRATE_ANON_DRIVE': { - Store.migrateAnonDrive(data, cb); break; - } - // RPC - case 'INIT_RPC': { - Store.initRpc(data, cb); break; - } - case 'UPDATE_PIN_LIMIT': { - Store.updatePinLimit(data, cb); break; - } - case 'GET_PIN_LIMIT': { - Store.getPinLimit(data, cb); break; - } - case 'CLEAR_OWNED_CHANNEL': { - Store.clearOwnedChannel(data, cb); break; - } - case 'REMOVE_OWNED_CHANNEL': { - Store.removeOwnedChannel(data, cb); break; - } - case 'UPLOAD_CHUNK': { - Store.uploadChunk(data, cb); break; - } - case 'UPLOAD_COMPLETE': { - Store.uploadComplete(data, cb); break; - } - case 'UPLOAD_STATUS': { - Store.uploadStatus(data, cb); break; - } - case 'UPLOAD_CANCEL': { - Store.uploadCancel(data, cb); break; - } - case 'PIN_PADS': { - Store.pinPads(data, cb); break; - } - case 'UNPIN_PADS': { - Store.unpinPads(data, cb); break; - } - case 'GET_DELETED_PADS': { - Store.getDeletedPads(data, cb); break; - } - case 'GET_PINNED_USAGE': { - Store.getPinnedUsage(data, cb); break; - } - // ANON RPC - case 'INIT_ANON_RPC': { - Store.initAnonRpc(data, cb); break; - } - case 'ANON_RPC_MESSAGE': { - Store.anonRpcMsg(data, cb); break; - } - case 'GET_FILE_SIZE': { - Store.getFileSize(data, cb); break; - } - case 'GET_MULTIPLE_FILE_SIZE': { - Store.getMultipleFileSize(data, cb); break; - } - // Store - case 'GET': { - Store.get(data, cb); break; - } - case 'SET': { - Store.set(data, cb); break; - } - case 'ADD_PAD': { - Store.addPad(data, cb); break; - } - case 'SET_PAD_TITLE': { - Store.setPadTitle(data, cb); break; - } - case 'MOVE_TO_TRASH': { - Store.moveToTrash(data, cb); break; - } - case 'RESET_DRIVE': { - Store.resetDrive(data, cb); break; - } - case 'GET_METADATA': { - Store.getMetadata(data, cb); break; - } - case 'SET_DISPLAY_NAME': { - Store.setDisplayName(data, cb); break; - } - case 'SET_PAD_ATTRIBUTE': { - Store.setPadAttribute(data, cb); break; - } - case 'GET_PAD_ATTRIBUTE': { - Store.getPadAttribute(data, cb); break; - } - case 'SET_ATTRIBUTE': { - Store.setAttribute(data, cb); break; - } - case 'GET_ATTRIBUTE': { - Store.getAttribute(data, cb); break; - } - case 'LIST_ALL_TAGS': { - Store.listAllTags(data, cb); break; - } - case 'GET_TEMPLATES': { - Store.getTemplates(data, cb); break; - } - case 'GET_SECURE_FILES_LIST': { - Store.getSecureFilesList(data, cb); break; - } - case 'GET_PAD_DATA': { - Store.getPadData(data, cb); break; - } - case 'GET_STRONGER_HASH': { - Store.getStrongerHash(data, cb); break; - } - case 'INCREMENT_TEMPLATE_USE': { - Store.incrementTemplateUse(data); break; - } - // Messaging - case 'INVITE_FROM_USERLIST': { - Store.inviteFromUserlist(data, cb); break; - } - // Messenger - case 'CONTACTS_GET_FRIEND_LIST': { - Store.messenger.getFriendList(data, cb); break; - } - case 'CONTACTS_GET_MY_INFO': { - Store.messenger.getMyInfo(data, cb); break; - } - case 'CONTACTS_GET_FRIEND_INFO': { - Store.messenger.getFriendInfo(data, cb); break; - } - case 'CONTACTS_REMOVE_FRIEND': { - Store.messenger.removeFriend(data, cb); break; - } - case 'CONTACTS_OPEN_FRIEND_CHANNEL': { - Store.messenger.openFriendChannel(data, cb); break; - } - case 'CONTACTS_GET_FRIEND_STATUS': { - Store.messenger.getFriendStatus(data, cb); break; - } - case 'CONTACTS_GET_MORE_HISTORY': { - Store.messenger.getMoreHistory(data, cb); break; - } - case 'CONTACTS_SEND_MESSAGE': { - Store.messenger.sendMessage(data, cb); break; - } - case 'CONTACTS_SET_CHANNEL_HEAD': { - Store.messenger.setChannelHead(data, cb); break; - } - // Pad - case 'SEND_PAD_MSG': { - Store.sendPadMsg(data, cb); break; - } - case 'JOIN_PAD': { - Store.joinPad(data, cb); break; - } - case 'GET_FULL_HISTORY': { - Store.getFullHistory(data, cb); break; - } - // Drive - case 'DRIVE_USEROBJECT': { - Store.userObjectCommand(data, cb); break; - } - // Settings - case 'DELETE_ACCOUNT': { - Store.deleteAccount(data, cb); break; - } - case 'IS_NEW_CHANNEL': { - Store.isNewChannel(data, cb); break; - } - default: { - console.error("UNHANDLED_STORE_RPC"); - - break; - } - }*/ - }; + // Internal calls + Rpc._removeClient = Store._removeClient; + Rpc._subscribeToDrive = Store._subscribeToDrive; + Rpc._subscribeToMessenger = Store._subscribeToMessenger; + return Rpc; }); diff --git a/www/common/outer/webworker.js b/www/common/outer/webworker.js index 81cd2fa9f..d8ed326c3 100644 --- a/www/common/outer/webworker.js +++ b/www/common/outer/webworker.js @@ -32,11 +32,14 @@ require([ Channel.create(msgEv, postMessage, function (chan) { console.log('ww ready'); + var clientId = '1'; Object.keys(Rpc.queries).forEach(function (q) { if (q === 'CONNECT') { return; } + if (q === 'JOIN_PAD') { return; } + if (q === 'SEND_PAD_MSG') { return; } chan.on(q, function (data, cb) { try { - Rpc.queries[q](data, cb); + Rpc.queries[q](clientId, data, cb); } catch (e) { console.error('Error in webworker when executing query ' + q); console.error(e); @@ -47,13 +50,59 @@ require([ 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) { + // cId is a clientId used in ServiceWorker or SharedWorker + cfg.query = function (cId, cmd, data, cb) { + cb = cb || function () {}; + chan.query(cmd, data, function (err, data2) { if (err) { return void cb({error: err}); } - cb(data); + cb(data2); }); }; - Rpc.queries['CONNECT'](cfg, cb); + cfg.broadcast = function (excludes, cmd, data, cb) { + cb = cb || function () {}; + if (excludes.indexOf(clientId) !== -1) { return; } + chan.query(cmd, data, function (err, data2) { + if (err) { return void cb({error: err}); } + cb(data2); + }); + }; + Rpc.queries['CONNECT'](clientId, cfg, function (data) { + console.log(data); + if (data && data.state === "ALREADY_INIT") { + return void cb(data); + } + if (cfg.driveEvents) { + Rpc._subscribeToDrive(clientId); + } + if (cfg.messenger) { + Rpc._subscribeToMessenger(clientId); + } + cb(data); + }); + }); + var chanId; + chan.on('JOIN_PAD', function (data, cb) { + chanId = data.channel; + try { + Rpc.queries['JOIN_PAD'](clientId, data, cb); + } catch (e) { + console.error('Error in webworker when executing query JOIN_PAD'); + console.error(e); + console.log(data); + } + }); + chan.on('SEND_PAD_MSG', function (msg, cb) { + var data = { + msg: msg, + channel: chanId + }; + try { + Rpc.queries['SEND_PAD_MSG'](clientId, data, cb); + } catch (e) { + console.error('Error in webworker when executing query SEND_PAD_MSG'); + console.error(e); + console.log(data); + } }); }, true); diff --git a/www/common/outer/worker-channel.js b/www/common/outer/worker-channel.js index c93fc6308..a2129bd69 100644 --- a/www/common/outer/worker-channel.js +++ b/www/common/outer/worker-channel.js @@ -24,7 +24,7 @@ define([ var txid = mkTxid(); var timeout = setTimeout(function () { delete queries[txid]; - console.log("Timeout making query " + q); + //console.log("Timeout making query " + q); }, 30000); queries[txid] = function (data, msg) { clearTimeout(timeout); diff --git a/www/worker/inner.js b/www/worker/inner.js index 6625960d5..20992b023 100644 --- a/www/worker/inner.js +++ b/www/worker/inner.js @@ -71,12 +71,19 @@ define([ // Service worker if ('serviceWorker' in navigator) { + console.log('here'); + var initializing = true; + var worker; var postMessage = function (data) { - if (navigator.serviceWorker && navigator.serviceWorker.controller) { - navigator.serviceWorker.controller.postMessage(data); + console.log(data, navigator.serviceWorker); + if (worker) { + return void worker.postMessage(data); } + console.log('NOT READY'); + /*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); @@ -84,6 +91,19 @@ define([ $container.append('
'); $container.append('Registered! (scope: ' + reg.scope +')'); reg.onupdatefound = function () { + if (initializing) { + var w = reg.installing; + var onStateChange = function () { + if (w.state === "activated") { + console.log(w); + worker = w; + postMessage("INIT"); + w.removeEventListener("statechange", onStateChange); + } + }; + w.addEventListener('statechange', onStateChange); + return; + } console.log('new SW version found!'); // KILL EVERYTHING UI.confirm("New version detected, you have to reload", function (yes) { @@ -94,6 +114,7 @@ define([ navigator.serviceWorker.addEventListener('message', function (e) { var data = e.data; if (data && data.state === "READY") { + initializing = false; $container.append('
sw.js ready'); postMessage(["Hello worker"]); return; @@ -101,7 +122,10 @@ define([ $container.append('
'); $container.append(e.data); }); - postMessage("INIT"); + if (reg.active) { + worker = reg.active; + postMessage("INIT"); + } }).catch(function(error) { console.log('Registration failed with ' + error); $container.append('Registration error: ' + error); diff --git a/www/worker/sw.js b/www/worker/sw.js index 7f102e49f..c122ae225 100644 --- a/www/worker/sw.js +++ b/www/worker/sw.js @@ -1,4 +1,5 @@ -var id = Math.floor(Math.random()*100000); +var id; +//= Math.floor(Math.random()*100000); var postMsg = function (client, data) { client.postMessage(data); @@ -42,6 +43,9 @@ self.addEventListener('message', function (e) { console.log(e.source); var cId = e.source.id; if (e.data === "INIT") { + if (!id) { + id = Math.floor(Math.random()*100000); + } broadcast(cId + ' has joined!', [cId]); postMsg(e.source, {state: 'READY'}); postMsg(e.source, "Welcome to SW " + id + "!");