From b9f5a0f52bd0261bb10a6920b0c60a16d6e91877 Mon Sep 17 00:00:00 2001 From: yflory Date: Thu, 31 May 2018 18:22:16 +0200 Subject: [PATCH 01/14] Move async store in a webworker! --- www/common/common-messaging.js | 18 ++-- www/common/cryptpad-common.js | 137 +++++++++++++++++++++++++++--- www/common/outer/async-store.js | 8 +- www/common/outer/store-rpc.js | 76 ++++++++++++++++- www/common/sframe-common-outer.js | 3 + 5 files changed, 215 insertions(+), 27 deletions(-) diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js index b5a12da0b..1c75927fc 100644 --- a/www/common/common-messaging.js +++ b/www/common/common-messaging.js @@ -89,7 +89,7 @@ define([ }; /* Used to accept friend requests within apps other than /contacts/ */ - Msg.addDirectMessageHandler = function (cfg) { + Msg.addDirectMessageHandler = function (cfg, href) { var network = cfg.network; var proxy = cfg.proxy; if (!network) { return void console.error('Network not ready'); } @@ -97,13 +97,12 @@ define([ var msg; if (sender === network.historyKeeper) { return; } try { - var parsed = Hash.parsePadUrl(window.location.href); + var parsed = Hash.parsePadUrl(href); + var secret = Hash.getSecrets(parsed.type, parsed.hash); if (!parsed.hashData) { return; } - var chan = Hash.hrefToHexChannelId(window.location.href); + var chan = secret.channel; // Decrypt - var keyStr = parsed.hashData.key; - var cryptor = Crypto.createEditCryptor(keyStr); - var key = cryptor.cryptKey; + var key = secret.keys ? secret.keys.cryptKey : Hash.decodeBase64(secret.key); var decryptMsg; try { decryptMsg = Crypto.decrypt(message, key); @@ -197,15 +196,14 @@ define([ var network = cfg.network; var netfluxId = data.netfluxId; var parsed = Hash.parsePadUrl(data.href); + var secret = Hash.getSecrets(parsed.type, parsed.hash); if (!parsed.hashData) { return; } // Message - var chan = Hash.hrefToHexChannelId(data.href); + var chan = secret.channel; var myData = createData(cfg.proxy); var msg = ["FRIEND_REQ", chan, myData]; // Encryption - var keyStr = parsed.hashData.key; - var cryptor = Crypto.createEditCryptor(keyStr); - var key = cryptor.cryptKey; + var key = secret.keys ? secret.keys.cryptKey : Hash.decodeBase64(secret.key); var msgStr = Crypto.encrypt(JSON.stringify(msg), key); // Send encrypted message if (pendingRequests.indexOf(netfluxId) === -1) { diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index f91f050e8..77707860c 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -7,12 +7,13 @@ define([ '/common/common-constants.js', '/common/common-feedback.js', '/common/outer/local-store.js', - '/common/outer/store-rpc.js', + //'/common/outer/store-rpc.js', + '/common/outer/worker-channel.js', '/customize/application_config.js', '/bower_components/nthen/index.js', ], function (Config, Messages, Util, Hash, - Messaging, Constants, Feedback, LocalStore, AStore, + Messaging, Constants, Feedback, LocalStore, /*AStore, */Channel, AppConfig, Nthen) { @@ -22,10 +23,11 @@ define([ Additionally, there is some basic functionality for import/export. */ - var postMessage = function (cmd, data, cb) { - setTimeout(function () { + var postMessage = function (/*cmd, data, cb*/) { + /*setTimeout(function () { AStore.query(cmd, data, cb); - }); + });*/ + console.error('NOT_READY'); }; var tryParsing = function (x) { try { return JSON.parse(x); } @@ -510,6 +512,11 @@ define([ var messaging = common.messaging = {}; messaging.onFriendRequest = Util.mkEvent(); messaging.onFriendComplete = Util.mkEvent(); + messaging.addHandlers = function (href) { + postMessage("ADD_DIRECT_MESSAGE_HANDLERS", { + href: href + }); + }; // Messenger var messenger = common.messenger = {}; @@ -641,14 +648,58 @@ define([ window.location.href = '/login/'; }; - common.startAccountDeletion = function (cb) { + common.startAccountDeletion = function (data, cb) { // Logout other tabs LocalStore.logout(null, true); cb(); }; + var queries = { + REQUEST_LOGIN: requestLogin, + UPDATE_METADATA: common.changeMetadata, + UPDATE_TOKEN: function (data) { + var localToken = tryParsing(localStorage.getItem(Constants.tokenKey)); + if (localToken !== data.token) { requestLogin(); } + }, + // Messaging + Q_FRIEND_REQUEST: common.messaging.onFriendRequest.fire, + EV_FIREND_COMPLETE: common.messaging.onFriendComplete.fire, + // Network + NETWORK_DISCONNECT: common.onNetworkDisconnect.fire, + NETWORK_RECONNECT: common.onNetworkReconnect.fire, + // Messenger + CONTACTS_MESSAGE: common.messenger.onMessageEvent.fire, + CONTACTS_JOIN: common.messenger.onJoinEvent.fire, + CONTACTS_LEAVE: common.messenger.onLeaveEvent.fire, + CONTACTS_UPDATE: common.messenger.onUpdateEvent.fire, + CONTACTS_FRIEND: common.messenger.onFriendEvent.fire, + CONTACTS_UNFRIEND: common.messenger.onUnfriendEvent.fire, + // Pad + PAD_READY: common.padRpc.onReadyEvent.fire, + PAD_MESSAGE: common.padRpc.onMessageEvent.fire, + PAD_JOIN: common.padRpc.onJoinEvent.fire, + PAD_LEAVE: common.padRpc.onLeaveEvent.fire, + PAD_DISCONNECT: common.padRpc.onDisconnectEvent.fire, + PAD_ERROR: common.padRpc.onErrorEvent.fire, + // Drive + DRIVE_LOG: common.drive.onLog.fire, + DRIVE_CHANGE: common.drive.onChange.fire, + DRIVE_REMOVE: common.drive.onRemove.fire, + // Account deletion + DELETE_ACCOUNT: common.startAccountDeletion, + // Loading + 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(); @@ -735,7 +786,7 @@ define([ common.loading.onDriveEvent.fire(data); break; } } - }; + };*/ common.ready = (function () { var env = {}; @@ -792,7 +843,8 @@ define([ } }).nThen(function (waitFor) { var cfg = { - query: onMessage, // TODO temporary, will be replaced by a webworker channel + init: true, + //query: onMessage, // TODO temporary, will be replaced by a webworker channel userHash: LocalStore.getUserHash(), anonHash: LocalStore.getFSHash(), localToken: tryParsing(localStorage.getItem(Constants.tokenKey)), @@ -804,7 +856,7 @@ define([ cfg.initialPath = sessionStorage[Constants.newPadPathKey]; delete sessionStorage[Constants.newPadPathKey]; } - AStore.query("CONNECT", cfg, waitFor(function (data) { + /*AStore.query("CONNECT", cfg, waitFor(function (data) { if (data.error) { throw new Error(data.error); } if (data.state === 'ALREADY_INIT') { data = data.returned; @@ -812,11 +864,11 @@ define([ if (data.anonHash && !cfg.userHash) { LocalStore.setFSHash(data.anonHash); } - /*if (cfg.userHash && sessionStorage) { + / *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)); @@ -828,7 +880,68 @@ define([ initFeedback(data.feedback); initialized = true; - })); + }));*/ + + 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); + } + }); + }); + + 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]); + } + } + + initFeedback(data.feedback); + initialized = true; + })); + + }), false); + }).nThen(function (waitFor) { // Load the new pad when the hash has changed var oldHref = document.location.href; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 680712f17..664ad56af 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -828,6 +828,10 @@ define([ var messagingCfg = getMessagingCfg(); Messaging.inviteFromUserlist(messagingCfg, data, cb); }; + Store.addDirectMessageHandlers = function (data) { + var messagingCfg = getMessagingCfg(); + Messaging.addDirectMessageHandler(messagingCfg, data.href); + }; // Messenger @@ -976,7 +980,6 @@ define([ channel.sendMessage(data, cb); }; - // TODO // GET_FULL_HISTORY from sframe-common-outer Store.getFullHistory = function (data, cb) { var network = store.network; @@ -1233,9 +1236,6 @@ define([ callback(ret); - var messagingCfg = getMessagingCfg(); - Messaging.addDirectMessageHandler(messagingCfg); - // Send events whenever there is a change or a removal in the drive if (data.driveEvents) { store.proxy.on('change', [], function (o, n, p) { diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index b134435b2..94a6eee85 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -3,7 +3,81 @@ define([ ], function (Store) { var Rpc = {}; + var queries = Rpc.queries = { + // Ready + CONNECT: Store.init, + DISCONNECT: Store.disconnect, + CREATE_README: Store.createReadme, + MIGRATE_ANON_DRIVE: Store.migrateAnonDrive, + // RPC + INIT_RPC: Store.initRpc, + UPDATE_PIN_LIMIT: Store.updatePinLimit, + GET_PIN_LIMIT: Store.getPinLimit, + CLEAR_OWNED_CHANNEL: Store.clearOwnedChannel, + REMOVE_OWNED_CHANNEL: Store.removeOwnedChannel, + UPLOAD_CHUNK: Store.uploadChunk, + UPLOAD_COMPLETE: Store.uploadComplete, + UPLOAD_STATUS: Store.uploadStatus, + UPLOAD_CANCEL: Store.uploadCancel, + PIN_PADS: Store.pinPads, + UNPIN_PADS: Store.unpinPads, + GET_DELETED_PADS: Store.getDeletedPads, + GET_PINNED_USAGE: Store.getPinnedUsage, + // ANON RPC + INIT_ANON_RPC: Store.initAnonRpc, + ANON_RPC_MESSAGE: Store.anonRpcMsg, + GET_FILE_SIZE: Store.getFileSize, + GET_MULTIPLE_FILE_SIZE: Store.getMultipleFileSize, + // Store + GET: Store.get, + SET: Store.set, + ADD_PAD: Store.addPad, + SET_PAD_TITLE: Store.setPadTitle, + MOVE_TO_TRASH: Store.moveToTrash, + RESET_DRIVE: Store.resetDrive, + GET_METADATA: Store.getMetadata, + SET_DISPLAY_NAME: Store.setDisplayName, + SET_PAD_ATTRIBUTE: Store.setPadAttribute, + GET_PAD_ATTRIBUTE: Store.getPadAttribute, + SET_ATTRIBUTE: Store.setAttribute, + GET_ATTRIBUTE: Store.getAttribute, + LIST_ALL_TAGS: Store.listAllTags, + GET_TEMPLATES: Store.getTemplates, + GET_SECURE_FILES_LIST: Store.getSecureFilesList, + GET_PAD_DATA: Store.getPadData, + GET_STRONGER_HASH: Store.getStrongerHash, + INCREMENT_TEMPLATE_USE: Store.incrementTemplateUse, + // Messaging + INVITE_FROM_USERLIST: Store.inviteFromUserlist, + ADD_DIRECT_MESSAGE_HANDLERS: Store.addDirectMessageHandlers, + // Messenger + CONTACTS_GET_FRIEND_LIST: Store.messenger.getFriendList, + CONTACTS_GET_MY_INFO: Store.messenger.getMyInfo, + CONTACTS_GET_FRIEND_INFO: Store.messenger.getFriendInfo, + CONTACTS_REMOVE_FRIEND: Store.messenger.removeFriend, + CONTACTS_OPEN_FRIEND_CHANNEL: Store.messenger.openFriendChannel, + CONTACTS_GET_FRIEND_STATUS: Store.messenger.getFriendStatus, + CONTACTS_GET_MORE_HISTORY: Store.messenger.getMoreHistory, + CONTACTS_SEND_MESSAGE: Store.messenger.sendMessage, + CONTACTS_SET_CHANNEL_HEAD: Store.messenger.setChannelHead, + // Pad + SEND_PAD_MSG: Store.sendPadMsg, + JOIN_PAD: Store.joinPad, + GET_FULL_HISTORY: Store.getFullHistory, + IS_NEW_CHANNEL: Store.isNewChannel, + // Drive + DRIVE_USEROBJECT: Store.userObjectCommand, + // Settings, + DELETE_ACCOUNT: Store.deleteAccount, + }; + Rpc.query = function (cmd, data, cb) { + if (queries[cmd]) { + queries[cmd](data, cb); + } else { + console.error('UNHANDLED_STORE_RPC'); + } + /* switch (cmd) { // READY case 'CONNECT': { @@ -184,7 +258,7 @@ define([ break; } - } + }*/ }; diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index c78bf53f9..60bc142a8 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -699,6 +699,9 @@ define([ readOnly: readOnly, crypto: Crypto.createEncryptor(secret.keys), onConnect: function () { + var href = parsed.getUrl(); + // Add friends requests handlers when we have the final href + Cryptpad.messaging.addHandlers(href); if (window.location.hash && window.location.hash !== '#') { window.location = parsed.getUrl({ present: parsed.hashData.present, From 9c5ad795e14e206321965fb4623d836280f444c4 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 1 Jun 2018 19:23:30 +0200 Subject: [PATCH 02/14] 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 From 6ab29f8f3aec11d970b2345000b98ddee3bdebe0 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 6 Jun 2018 15:58:43 +0200 Subject: [PATCH 03/14] 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 + "!"); From ce6779a06f40c05d72cb3a07beaeb17275092e1a Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 6 Jun 2018 17:11:30 +0200 Subject: [PATCH 04/14] Remove debugging logs --- www/common/outer/async-store.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index f26eb50b4..6db2b47ac 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -50,7 +50,6 @@ define([ } else { obj[key] = data.value; } - console.log('broadcasting displayName'); broadcast([clientId], "UPDATE_METADATA"); onSync(cb); }; @@ -1060,14 +1059,11 @@ define([ CpNfWorker.start(conf); }; 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...'); + if (!channel) { return; } if (!channel.wc) { - console.log('no WC, push to queue'); channel.queue.push(msg); return void cb(); } @@ -1168,8 +1164,6 @@ define([ 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); From 953d98be96d65d1e7119ad3f25c5ead11ffdd745 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 6 Jun 2018 17:25:06 +0200 Subject: [PATCH 05/14] Fix reconnect --- www/common/cryptpad-common.js | 6 ++++-- www/common/outer/async-store.js | 2 +- www/common/outer/worker-channel.js | 2 +- www/common/sframe-chainpad-netflux-outer.js | 6 ++++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 77707860c..e550671b0 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -556,8 +556,8 @@ define([ // Pad RPC var pad = common.padRpc = {}; - pad.joinPad = function (data, cb) { - postMessage("JOIN_PAD", data, cb); + pad.joinPad = function (data) { + postMessage("JOIN_PAD", data); }; pad.sendPadMsg = function (data, cb) { postMessage("SEND_PAD_MSG", data, cb); @@ -567,6 +567,7 @@ define([ pad.onJoinEvent = Util.mkEvent(); pad.onLeaveEvent = Util.mkEvent(); pad.onDisconnectEvent = Util.mkEvent(); + pad.onConnectEvent = Util.mkEvent(); pad.onErrorEvent = Util.mkEvent(); // Loading events @@ -680,6 +681,7 @@ define([ PAD_JOIN: common.padRpc.onJoinEvent.fire, PAD_LEAVE: common.padRpc.onLeaveEvent.fire, PAD_DISCONNECT: common.padRpc.onDisconnectEvent.fire, + PAD_CONNECT: common.padRpc.onConnectEvent.fire, PAD_ERROR: common.padRpc.onErrorEvent.fire, // Drive DRIVE_LOG: common.drive.onLog.fire, diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 664ad56af..eb8748c6f 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -966,7 +966,7 @@ define([ channel.queue.forEach(function (data) { sendMessage(data.message); }); - cb({ + postMessage("PAD_CONNECT", { myID: wc.myID, id: wc.id, members: wc.members 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/common/sframe-chainpad-netflux-outer.js b/www/common/sframe-chainpad-netflux-outer.js index 45c4bdabf..b08f1fe08 100644 --- a/www/common/sframe-chainpad-netflux-outer.js +++ b/www/common/sframe-chainpad-netflux-outer.js @@ -116,6 +116,10 @@ define([], function () { sframeChan.event('EV_RT_DISCONNECT'); }); + padRpc.onConnectEvent.reg(function (data) { + onOpen(data); + }); + padRpc.onErrorEvent.reg(function (err) { sframeChan.event('EV_RT_ERROR', err); }); @@ -128,8 +132,6 @@ define([], function () { owners: owners, password: password, expire: expire - }, function(data) { - onOpen(data); }); }; From 598d56c75e88e22be07abb4875d60f36161e322f Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 6 Jun 2018 17:45:43 +0200 Subject: [PATCH 06/14] Fix cache issues --- www/common/cryptpad-common.js | 5 +- www/common/outer/webworker.js | 89 +++++++++++++++-------------------- 2 files changed, 42 insertions(+), 52 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index e550671b0..53600499e 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -16,13 +16,14 @@ define([ Messaging, Constants, Feedback, LocalStore, /*AStore, */Channel, AppConfig, Nthen) { - /* This file exposes functionality which is specific to Cryptpad, but not to any particular pad type. This includes functions for committing metadata about pads to your local storage for future use and improved usability. Additionally, there is some basic functionality for import/export. */ + var urlArgs = Util.find(Config, ['requireConf', 'urlArgs']) || ''; + var postMessage = function (/*cmd, data, cb*/) { /*setTimeout(function () { AStore.query(cmd, data, cb); @@ -885,7 +886,7 @@ define([ }));*/ var msgEv = Util.mkEvent(); - var worker = new Worker('/common/outer/webworker.js'); + var worker = new Worker('/common/outer/webworker.js?' + urlArgs); worker.onmessage = function (ev) { msgEv.fire(ev); }; diff --git a/www/common/outer/webworker.js b/www/common/outer/webworker.js index 81cd2fa9f..fcdfdc6cb 100644 --- a/www/common/outer/webworker.js +++ b/www/common/outer/webworker.js @@ -1,63 +1,52 @@ /* 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(); + '/common/requireconfig.js' +], function (RequireConfig) { + require.config(RequireConfig()); + 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); + 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); + } }); - }; - Rpc.queries['CONNECT'](cfg, cb); - }); - }, true); + }); + 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); - }; + onmessage = function (e) { + msgEv.fire(e); + }; + }); }); From a6d9ecb3ba270d2e8167f8a9cfb690b13f43e611 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 6 Jun 2018 17:48:10 +0200 Subject: [PATCH 07/14] lint compliance --- www/common/outer/async-store.js | 2 +- www/worker/sw.js | 2 ++ www/worker2 | 1 - 3 files changed, 3 insertions(+), 2 deletions(-) delete mode 120000 www/worker2 diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index eb8748c6f..d7c8489d4 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -928,7 +928,7 @@ define([ queue: [], data: {} }; - Store.joinPad = function (data, cb) { + Store.joinPad = function (data) { var conf = { onReady: function (padData) { channel.data = padData || {}; diff --git a/www/worker/sw.js b/www/worker/sw.js index 7f102e49f..8790de68c 100644 --- a/www/worker/sw.js +++ b/www/worker/sw.js @@ -1,3 +1,5 @@ +/* jshint ignore:start */ + var id = Math.floor(Math.random()*100000); var postMsg = function (client, data) { diff --git a/www/worker2 b/www/worker2 deleted file mode 120000 index b2b186dda..000000000 --- a/www/worker2 +++ /dev/null @@ -1 +0,0 @@ -worker/ \ No newline at end of file From 305b47132cce982e5d3bb0627982b4ea2d98f622 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 6 Jun 2018 17:55:02 +0200 Subject: [PATCH 08/14] lint compliance --- www/common/outer/async-store.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 1057dee08..5e6b1b261 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -854,7 +854,7 @@ define([ var messagingCfg = getMessagingCfg(clientId); Messaging.inviteFromUserlist(messagingCfg, data, cb); }; - Store.addDirectMessageHandlers = function (clientId, data, cb) { + Store.addDirectMessageHandlers = function (clientId, data) { var messagingCfg = getMessagingCfg(clientId); Messaging.addDirectMessageHandler(messagingCfg, data.href); }; @@ -966,7 +966,7 @@ define([ pushHistory: function (msg, isCp) { if (isCp) { channel.history.push('cp|' + msg); - var i, cpCount = 1; + var i; for (i = channel.history.length - 2; i > 0; i--) { if (/^cp\|/.test(channel.history[i])) { break; } } From 48dc8c78b4557fd3bd7ac74e5986476c117b25bb Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 6 Jun 2018 18:10:58 +0200 Subject: [PATCH 09/14] Fix cache and merge issues --- www/common/cryptpad-common.js | 10 +- www/common/outer/async-store.js | 2 +- www/common/outer/serviceworker.js | 200 ++++++++++++------------ www/common/outer/sharedworker.js | 243 ++++++++++++------------------ 4 files changed, 198 insertions(+), 257 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 719f5486b..b04660c51 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -769,8 +769,10 @@ define([ var postMsg, worker; Nthen(function (waitFor2) { if (SharedWorker) { - worker = new SharedWorker('/common/outer/sharedworker.js' + urlArgs); - console.log(worker); + worker = new SharedWorker('/common/outer/sharedworker.js?' + urlArgs); + worker.onerror = function (e) { + console.error(e); + }; worker.port.onmessage = function (ev) { if (ev.data === "SW_READY") { return; @@ -789,7 +791,7 @@ define([ if (worker) { return void worker.postMessage(data); } }; - navigator.serviceWorker.register('/common/outer/serviceworker.js' + urlArgs, {scope: '/'}) + navigator.serviceWorker.register('/common/outer/serviceworker.js?' + urlArgs, {scope: '/'}) .then(function(reg) { // Add handler for receiving messages from the service worker navigator.serviceWorker.addEventListener('message', function (ev) { @@ -829,7 +831,7 @@ define([ /**/console.log('Registration failed with ' + error); }); } else if (Worker) { - worker = new Worker('/common/outer/webworker.js' + urlArgs); + worker = new Worker('/common/outer/webworker.js?' + urlArgs); worker.onmessage = function (ev) { msgEv.fire(ev); }; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 5e6b1b261..172ff406d 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1049,7 +1049,7 @@ define([ channel.queue.forEach(function (data) { channel.sendMessage(data.message, clientId); }); - postMessage("PAD_CONNECT", { + channel.bcast("PAD_CONNECT", { myID: wc.myID, id: wc.id, members: wc.members diff --git a/www/common/outer/serviceworker.js b/www/common/outer/serviceworker.js index d7660c311..9c56a1250 100644 --- a/www/common/outer/serviceworker.js +++ b/www/common/outer/serviceworker.js @@ -1,22 +1,5 @@ /* 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 = { @@ -24,11 +7,12 @@ localStorage = { getItem: function (k) { return localStorage[k]; } }; +self.tabs = {}; + var postMsg = function (client, data) { client.postMessage(data); }; - var debug = function (msg) { console.log(msg); }; // debug = function () {}; @@ -36,111 +20,115 @@ 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(); + '/common/requireconfig.js' + ], function (RequireConfig) { + require.config(RequireConfig()); + 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 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); - } + 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); + 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); } - 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; } + 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); }); - }; - Rpc.queries['CONNECT'](clientId, cfg, function (data) { - if (cfg.driveEvents) { - Rpc._subscribeToDrive(clientId); - } - if (cfg.messenger) { - Rpc._subscribeToMessenger(clientId); + }); + 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); } - if (data && data.state === "ALREADY_INIT") { - return void cb(data.returned); + }); + 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); } - 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); + cb(); + }, true); - self.tabs[client.id].msgEv = msgEv; + self.tabs[client.id].msgEv = msgEv; + }); }); }; -self.tabs = {}; self.addEventListener('message', function (e) { var cId = e.source.id; if (e.data === "INIT") { diff --git a/www/common/outer/sharedworker.js b/www/common/outer/sharedworker.js index 9d5aa1187..3ca82f262 100644 --- a/www/common/outer/sharedworker.js +++ b/www/common/outer/sharedworker.js @@ -1,23 +1,5 @@ -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 = { @@ -26,16 +8,6 @@ localStorage = { }; 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); @@ -48,114 +20,119 @@ 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); - } + '/common/requireconfig.js' + ], function (RequireConfig) { + require.config(RequireConfig()); + 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); + 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); } - 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; } + 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); }); - }; - Rpc.queries['CONNECT'](clientId, cfg, function (data) { - if (cfg.driveEvents) { - Rpc._subscribeToDrive(clientId); - } - if (cfg.messenger) { - Rpc._subscribeToMessenger(clientId); + }); + 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); } - if (data && data.state === "ALREADY_INIT") { - self.store = data.returned; - return void cb(data.returned); + }); + 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); } - 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); + cb(); + }, true); - client.msgEv = msgEv; + client.msgEv = msgEv; + }); }); }; onconnect = function(e) { - debug('New ShardWorker client'); + debug('New SharedWorker client'); var port = e.ports[0]; var cId = Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)) var client = self.tabs[cId] = { @@ -176,29 +153,3 @@ onconnect = function(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!'); -}); - - - From 78ff55e34e9bb55db832d6861597cb0edb46b3b6 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 6 Jun 2018 18:21:52 +0200 Subject: [PATCH 10/14] Fix race condition --- www/common/cryptpad-common.js | 1 + www/common/outer/async-store.js | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index b04660c51..41305240d 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -858,6 +858,7 @@ define([ }); postMessage = function (cmd, data, cb) { + cb = cb || function () {} chan.query(cmd, data, function (err, data) { if (err) { return void cb ({error: err}); } cb(data); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 172ff406d..1608c8eda 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -981,6 +981,11 @@ define([ } if (!isNew && channel.wc) { + postMessage(clientId, "PAD_CONNECT", { + myID: channel.wc.myID, + id: channel.wc.id, + members: channel.wc.members + }); channel.wc.members.forEach(function (m) { postMessage(clientId, "PAD_JOIN", m); }); @@ -992,11 +997,6 @@ define([ }); }); postMessage(clientId, "PAD_READY"); - cb({ - myID: channel.wc.myID, - id: channel.wc.id, - members: channel.wc.members - }); return; } From f05e2225d6d6a283d5f3bf97f61c71d18fa0da57 Mon Sep 17 00:00:00 2001 From: yflory Date: Wed, 6 Jun 2018 18:22:48 +0200 Subject: [PATCH 11/14] lint compliance --- www/common/cryptpad-common.js | 2 +- www/common/outer/async-store.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 41305240d..38b2c9318 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -858,7 +858,7 @@ define([ }); postMessage = function (cmd, data, cb) { - cb = cb || function () {} + cb = cb || function () {}; chan.query(cmd, data, function (err, data) { if (err) { return void cb ({error: err}); } cb(data); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 1608c8eda..d8b0c6642 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -950,7 +950,7 @@ define([ var channels = Store.channels = {}; - Store.joinPad = function (clientId, data, cb) { + Store.joinPad = function (clientId, data) { var isNew = typeof channels[data.channel] === "undefined"; var channel = channels[data.channel] = channels[data.channel] || { queue: [], From 02b282a1a5c2b499e24ad23820158d8569b07a63 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 8 Jun 2018 16:45:07 +0200 Subject: [PATCH 12/14] Disconnect from shared/service worker --- www/common/cryptpad-common.js | 10 +++++++++- www/common/outer/async-store.js | 11 +++++++++++ www/common/outer/serviceworker.js | 9 +++++++++ www/common/outer/sharedworker.js | 9 +++++++++ 4 files changed, 38 insertions(+), 1 deletion(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 38b2c9318..fd4288d4f 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -768,7 +768,7 @@ define([ var msgEv = Util.mkEvent(); var postMsg, worker; Nthen(function (waitFor2) { - if (SharedWorker) { + if (typeof(SharedWorker) !== "undefined") { worker = new SharedWorker('/common/outer/sharedworker.js?' + urlArgs); worker.onerror = function (e) { console.error(e); @@ -783,6 +783,10 @@ define([ worker.port.postMessage(data); }; postMsg('INIT'); + + window.addEventListener('beforeunload', function () { + postMsg('CLOSE'); + }); } else if (false && 'serviceWorker' in navigator) { var initializing = true; var stopWaiting = waitFor2(); // Call this function when we're ready @@ -830,6 +834,10 @@ define([ }).catch(function(error) { /**/console.log('Registration failed with ' + error); }); + + window.addEventListener('beforeunload', function () { + postMsg('CLOSE'); + }); } else if (Worker) { worker = new Worker('/common/outer/webworker.js?' + urlArgs); worker.onmessage = function (ev) { diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index d8b0c6642..3015e6ca1 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -1143,6 +1143,14 @@ define([ var driveEventClients = []; var messengerEventClients = []; + var dropChannel = function (chanId) { + if (!Store.channels[chanId]) { return; } + + if (Store.channels[chanId].wc) { + Store.channels[chanId].wc.leave(''); + } + delete Store.channels[chanId]; + }; Store._removeClient = function (clientId) { var driveIdx = driveEventClients.indexOf(clientId); if (driveIdx !== -1) { @@ -1157,6 +1165,9 @@ define([ if (chanIdx !== -1) { Store.channels[chanId].clients.splice(chanIdx, 1); } + if (Store.channels[chanId].clients.length === 0) { + dropChannel(chanId); + } }); }; diff --git a/www/common/outer/serviceworker.js b/www/common/outer/serviceworker.js index 9c56a1250..214df9abf 100644 --- a/www/common/outer/serviceworker.js +++ b/www/common/outer/serviceworker.js @@ -125,6 +125,10 @@ var init = function (client, cb) { }, true); self.tabs[client.id].msgEv = msgEv; + + self.tabs[client.id].close = function () { + Rpc._removeClient(client.id); + }; }); }); }; @@ -139,6 +143,11 @@ self.addEventListener('message', function (e) { init(e.source, function () { postMsg(e.source, 'SW_READY'); }); + } else if (e.data === "CLOSE") { + if (tabs[cId] && tabs[cId].close) { + console.log('leave'); + tabs[cId].close(); + } } else if (self.tabs[cId] && self.tabs[cId].msgEv) { self.tabs[cId].msgEv.fire(e); } diff --git a/www/common/outer/sharedworker.js b/www/common/outer/sharedworker.js index 3ca82f262..14bfa1250 100644 --- a/www/common/outer/sharedworker.js +++ b/www/common/outer/sharedworker.js @@ -127,6 +127,10 @@ var init = function (client, cb) { }, true); client.msgEv = msgEv; + + client.close = function () { + Rpc._removeClient(client.id); + }; }); }); }; @@ -147,6 +151,11 @@ onconnect = function(e) { init(client, function () { postMsg(client, 'SW_READY'); }); + } else if (e.data === "CLOSE") { + if (client && client.close) { + console.log('leave'); + client.close(); + } } else if (client && client.msgEv) { client.msgEv.fire(e); } From 47dee664daf7dda0caac8c16bd8f46d0787e9ec1 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 8 Jun 2018 17:35:44 +0200 Subject: [PATCH 13/14] Add support for login & logout while keeping the same shared worker --- www/common/cryptpad-common.js | 5 +- www/common/outer/async-store.js | 2512 +++++++++++++++-------------- www/common/outer/serviceworker.js | 13 +- www/common/outer/sharedworker.js | 12 +- www/common/outer/store-rpc.js | 169 +- www/common/outer/webworker.js | 4 +- 6 files changed, 1377 insertions(+), 1338 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index fd4288d4f..b94d31f59 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -932,9 +932,12 @@ define([ document.location.reload(); } else if (o && !n) { LocalStore.logout(); - postMessage("DISCONNECT"); } }); + LocalStore.onLogout(function () { + console.log('onLogout: disconnect'); + postMessage("DISCONNECT"); + }); if (PINNING_ENABLED && LocalStore.isLoggedIn()) { console.log("logged in. pads will be pinned"); diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js index 3015e6ca1..22fe9cbe1 100644 --- a/www/common/outer/async-store.js +++ b/www/common/outer/async-store.js @@ -23,1435 +23,1441 @@ define([ Crypto, ChainPad, Listmap, nThen, Saferphore) { var Store = {}; - var postMessage = function () {}; - var broadcast = function () {}; - var sendDriveEvent = function () {}; + var create = function () { + var postMessage = function () {}; + var broadcast = function () {}; + var sendDriveEvent = function () {}; - var storeHash; + var storeHash; - var store = window.CryptPad_AsyncStore = {}; + var store = window.CryptPad_AsyncStore = {}; - var onSync = function (cb) { - Realtime.whenRealtimeSyncs(store.realtime, cb); - }; + var onSync = function (cb) { + Realtime.whenRealtimeSyncs(store.realtime, cb); + }; - Store.get = function (clientId, key, cb) { - cb(Util.find(store.proxy, key)); - }; - Store.set = function (clientId, data, cb) { - var path = data.key.slice(); - var key = path.pop(); - var obj = Util.find(store.proxy, path); - if (!obj || typeof(obj) !== "object") { return void cb({error: 'INVALID_PATH'}); } - if (typeof data.value === "undefined") { - delete obj[key]; - } else { - obj[key] = data.value; - } - broadcast([clientId], "UPDATE_METADATA"); - onSync(cb); - }; + Store.get = function (clientId, key, cb) { + cb(Util.find(store.proxy, key)); + }; + Store.set = function (clientId, data, cb) { + var path = data.key.slice(); + var key = path.pop(); + var obj = Util.find(store.proxy, path); + if (!obj || typeof(obj) !== "object") { return void cb({error: 'INVALID_PATH'}); } + if (typeof data.value === "undefined") { + delete obj[key]; + } else { + obj[key] = data.value; + } + broadcast([clientId], "UPDATE_METADATA"); + onSync(cb); + }; - Store.hasSigningKeys = function () { - if (!store.proxy) { return; } - return typeof(store.proxy.edPrivate) === 'string' && - typeof(store.proxy.edPublic) === 'string'; - }; + Store.hasSigningKeys = function () { + if (!store.proxy) { return; } + return typeof(store.proxy.edPrivate) === 'string' && + typeof(store.proxy.edPublic) === 'string'; + }; - Store.hasCurveKeys = function () { - if (!store.proxy) { return; } - return typeof(store.proxy.curvePrivate) === 'string' && - typeof(store.proxy.curvePublic) === 'string'; - }; + Store.hasCurveKeys = function () { + if (!store.proxy) { return; } + return typeof(store.proxy.curvePrivate) === 'string' && + typeof(store.proxy.curvePublic) === 'string'; + }; - var getUserChannelList = function () { - // start with your userHash... - var userHash = storeHash; - if (!userHash) { return null; } - - // No password for drive - var secret = Hash.getSecrets('drive', userHash); - var userChannel = secret.channel; - if (!userChannel) { return null; } - - // Get the list of pads' channel ID in your drive - // This list is filtered so that it doesn't include pad owned by other users (you should - // not pin these pads) - var files = store.userObject.getFiles([store.userObject.FILES_DATA]); - var edPublic = store.proxy.edPublic; - var list = files.map(function (id) { - var d = store.userObject.getFileData(id); - if (d.owners && d.owners.length && edPublic && - d.owners.indexOf(edPublic) === -1) { return; } - return d.channel; - }) - .filter(function (x) { return x; }); - - // Get the avatar - var profile = store.proxy.profile; - if (profile) { - var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit, null) : null; - if (profileChan) { list.push(profileChan); } - var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar, null) : null; - if (avatarChan) { list.push(avatarChan); } - } - - if (store.proxy.friends) { - var fList = Messaging.getFriendChannelsList(store.proxy); - list = list.concat(fList); - } - - list.push(userChannel); - list.sort(); - - return list; - }; + var getUserChannelList = function () { + // start with your userHash... + var userHash = storeHash; + if (!userHash) { return null; } + + // No password for drive + var secret = Hash.getSecrets('drive', userHash); + var userChannel = secret.channel; + if (!userChannel) { return null; } - var getExpirableChannelList = function () { - var list = []; - store.userObject.getFiles([store.userObject.FILES_DATA]).forEach(function (id) { - var data = store.userObject.getFileData(id); + // Get the list of pads' channel ID in your drive + // This list is filtered so that it doesn't include pad owned by other users (you should + // not pin these pads) + var files = store.userObject.getFiles([store.userObject.FILES_DATA]); var edPublic = store.proxy.edPublic; + var list = files.map(function (id) { + var d = store.userObject.getFileData(id); + if (d.owners && d.owners.length && edPublic && + d.owners.indexOf(edPublic) === -1) { return; } + return d.channel; + }) + .filter(function (x) { return x; }); + + // Get the avatar + var profile = store.proxy.profile; + if (profile) { + var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit, null) : null; + if (profileChan) { list.push(profileChan); } + var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar, null) : null; + if (avatarChan) { list.push(avatarChan); } + } - // Push channels owned by someone else or channel that should have expired - // because of the expiration time - if ((data.owners && data.owners.length && data.owners.indexOf(edPublic) === -1) || - (data.expire && data.expire < (+new Date()))) { - list.push(data.channel); + if (store.proxy.friends) { + var fList = Messaging.getFriendChannelsList(store.proxy); + list = list.concat(fList); } - }); - return list; - }; - var getCanonicalChannelList = function (expirable) { - var list = expirable ? getExpirableChannelList() : getUserChannelList(); - return Util.deduplicateString(list).sort(); - }; + list.push(userChannel); + list.sort(); - ////////////////////////////////////////////////////////////////// - /////////////////////// RPC ////////////////////////////////////// - ////////////////////////////////////////////////////////////////// + return list; + }; - 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'); - } + var getExpirableChannelList = function () { + var list = []; + store.userObject.getFiles([store.userObject.FILES_DATA]).forEach(function (id) { + var data = store.userObject.getFileData(id); + var edPublic = store.proxy.edPublic; + + // Push channels owned by someone else or channel that should have expired + // because of the expiration time + if ((data.owners && data.owners.length && data.owners.indexOf(edPublic) === -1) || + (data.expire && data.expire < (+new Date()))) { + list.push(data.channel); + } + }); + return list; + }; - store.rpc.pin(data, function (e, hash) { - if (e) { return void cb({error: e}); } - cb({hash: hash}); - }); - }; + var getCanonicalChannelList = function (expirable) { + var list = expirable ? getExpirableChannelList() : getUserChannelList(); + return Util.deduplicateString(list).sort(); + }; - Store.unpinPads = function (clientId, data, cb) { - if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + ////////////////////////////////////////////////////////////////// + /////////////////////// RPC ////////////////////////////////////// + ////////////////////////////////////////////////////////////////// - store.rpc.unpin(data, function (e, hash) { - if (e) { return void cb({error: e}); } - cb({hash: hash}); - }); - }; + 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'); + } - var account = {}; + store.rpc.pin(data, function (e, hash) { + if (e) { return void cb({error: e}); } + cb({hash: hash}); + }); + }; - Store.getPinnedUsage = function (clientId, data, cb) { - if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + Store.unpinPads = function (clientId, data, cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } - store.rpc.getFileListSize(function (err, bytes) { - if (typeof(bytes) === 'number') { - account.usage = bytes; - } - cb({bytes: bytes}); - }); - }; + store.rpc.unpin(data, function (e, hash) { + if (e) { return void cb({error: e}); } + cb({hash: hash}); + }); + }; - // Update for all users from accounts and return current user limits - 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}); } - account.limit = limit; - account.plan = plan; - account.note = note; - cb(account); - }); - }; - // Get current user limits - Store.getPinLimit = function (clientId, data, cb) { - if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } - - var ALWAYS_REVALIDATE = true; - if (ALWAYS_REVALIDATE || typeof(account.limit) !== 'number' || - typeof(account.plan) !== 'string' || - typeof(account.note) !== 'string') { - return void store.rpc.getLimit(function (e, limit, plan, note) { + var account = {}; + + Store.getPinnedUsage = function (clientId, data, cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + + store.rpc.getFileListSize(function (err, bytes) { + if (typeof(bytes) === 'number') { + account.usage = bytes; + } + cb({bytes: bytes}); + }); + }; + + // Update for all users from accounts and return current user limits + 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}); } account.limit = limit; account.plan = plan; account.note = note; cb(account); }); - } - cb(account); - }; + }; + // Get current user limits + Store.getPinLimit = function (clientId, data, cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + + var ALWAYS_REVALIDATE = true; + if (ALWAYS_REVALIDATE || typeof(account.limit) !== 'number' || + typeof(account.plan) !== 'string' || + typeof(account.note) !== 'string') { + return void store.rpc.getLimit(function (e, limit, plan, note) { + if (e) { return void cb({error: e}); } + account.limit = limit; + account.plan = plan; + account.note = note; + cb(account); + }); + } + cb(account); + }; - 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.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 (clientId, data, cb) { - if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } - store.rpc.removeOwnedChannel(data, function (err) { - cb({error:err}); - }); - }; + 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}); + }); + }; - var arePinsSynced = function (cb) { - if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + var arePinsSynced = function (cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } - var list = getCanonicalChannelList(false); - var local = Hash.hashChannelList(list); - store.rpc.getServerHash(function (e, hash) { - if (e) { return void cb(e); } - cb(null, hash === local); - }); - }; + var list = getCanonicalChannelList(false); + var local = Hash.hashChannelList(list); + store.rpc.getServerHash(function (e, hash) { + if (e) { return void cb(e); } + cb(null, hash === local); + }); + }; - var resetPins = function (cb) { - if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + var resetPins = function (cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } - var list = getCanonicalChannelList(false); - store.rpc.reset(list, function (e, hash) { - if (e) { return void cb(e); } - cb(null, hash); - }); - }; + var list = getCanonicalChannelList(false); + store.rpc.reset(list, function (e, hash) { + if (e) { return void cb(e); } + cb(null, hash); + }); + }; - Store.uploadComplete = function (clientId, data, cb) { - if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } - if (data.owned) { - // Owned file - store.rpc.ownedUploadComplete(data.id, function (err, res) { + Store.uploadComplete = function (clientId, data, cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + if (data.owned) { + // Owned file + store.rpc.ownedUploadComplete(data.id, function (err, res) { + if (err) { return void cb({error:err}); } + cb(res); + }); + return; + } + // Normal upload + store.rpc.uploadComplete(data.id, function (err, res) { if (err) { return void cb({error:err}); } cb(res); }); - return; - } - // Normal upload - store.rpc.uploadComplete(data.id, function (err, res) { - if (err) { return void cb({error:err}); } - cb(res); - }); - }; + }; - 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}); } - cb(res); - }); - }; + 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}); } + cb(res); + }); + }; - 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}); } - cb(res); - }); - }; + 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}); } + cb(res); + }); + }; - Store.uploadChunk = function (clientId, data, cb) { - store.rpc.send.unauthenticated('UPLOAD', data.chunk, function (e, msg) { - cb({ - error: e, - msg: msg + Store.uploadChunk = function (clientId, data, cb) { + store.rpc.send.unauthenticated('UPLOAD', data.chunk, function (e, msg) { + cb({ + error: e, + msg: msg + }); }); - }); - }; + }; - 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.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.rpc = call; - Store.getPinLimit(null, null, function (obj) { - if (obj.error) { console.error(obj.error); } - account.limit = obj.limit; - account.plan = obj.plan; - account.note = obj.note; - cb(obj); - }); + Store.getPinLimit(null, null, function (obj) { + if (obj.error) { console.error(obj.error); } + account.limit = obj.limit; + account.plan = obj.plan; + account.note = obj.note; + cb(obj); + }); - arePinsSynced(function (err, yes) { - if (!yes) { - resetPins(function (err) { - if (err) { return console.error(err); } - console.log('RESET DONE'); - }); - } + arePinsSynced(function (err, yes) { + if (!yes) { + resetPins(function (err) { + if (err) { return console.error(err); } + console.log('RESET DONE'); + }); + } + }); }); }); - }); - }; + }; - ////////////////////////////////////////////////////////////////// - ////////////////// ANON RPC ////////////////////////////////////// - ////////////////////////////////////////////////////////////////// - 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}); } - cb(res); - }); - }; + ////////////////////////////////////////////////////////////////// + ////////////////// ANON RPC ////////////////////////////////////// + ////////////////////////////////////////////////////////////////// + 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}); } + cb(res); + }); + }; - Store.getFileSize = function (clientId, data, cb) { - if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); } + 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); - store.anon_rpc.send("GET_FILE_SIZE", channelId, function (e, response) { - if (e) { return void cb({error: e}); } - if (response && response.length && typeof(response[0]) === 'number') { - return void cb({size: response[0]}); - } else { - cb({error: 'INVALID_RESPONSE'}); - } - }); - }; + var channelId = Hash.hrefToHexChannelId(data.href, data.password); + store.anon_rpc.send("GET_FILE_SIZE", channelId, function (e, response) { + if (e) { return void cb({error: e}); } + if (response && response.length && typeof(response[0]) === 'number') { + return void cb({size: response[0]}); + } else { + cb({error: 'INVALID_RESPONSE'}); + } + }); + }; - 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) { - if (e) { return void cb({error: e}); } - if (response && response.length && typeof(response[0]) === 'boolean') { - return void cb({ - isNew: response[0] - }); - } else { - cb({error: 'INVALID_RESPONSE'}); + 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) { + if (e) { return void cb({error: e}); } + if (response && response.length && typeof(response[0]) === 'boolean') { + return void cb({ + isNew: response[0] + }); + } else { + cb({error: 'INVALID_RESPONSE'}); + } + }); + }; + + 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'}); } - }); - }; - 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'}); - } + store.anon_rpc.send('GET_MULTIPLE_FILE_SIZE', data.files, function (e, res) { + if (e) { return void cb({error: e}); } + if (res && res.length && typeof(res[0]) === 'object') { + cb({size: res[0]}); + } else { + cb({error: 'UNEXPECTED_RESPONSE'}); + } + }); + }; - store.anon_rpc.send('GET_MULTIPLE_FILE_SIZE', data.files, function (e, res) { - if (e) { return void cb({error: e}); } - if (res && res.length && typeof(res[0]) === 'object') { - cb({size: res[0]}); - } else { - cb({error: 'UNEXPECTED_RESPONSE'}); + 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)) { + return void cb({error: 'INVALID_FILE_LIST'}); } - }); - }; - 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)) { - return void cb({error: 'INVALID_FILE_LIST'}); - } - - store.anon_rpc.send('GET_DELETED_PADS', list, function (e, res) { - if (e) { return void cb({error: e}); } - if (res && res.length && Array.isArray(res[0])) { - cb(res[0]); - } else { - cb({error: 'UNEXPECTED_RESPONSE'}); - } - }); - }; - - Store.initAnonRpc = function (clientId, data, cb) { - if (store.anon_rpc) { return void cb(); } - require([ - '/common/rpc.js', - ], function (Rpc) { - Rpc.createAnonymous(store.network, function (e, call) { + store.anon_rpc.send('GET_DELETED_PADS', list, function (e, res) { if (e) { return void cb({error: e}); } - store.anon_rpc = call; - cb(); + if (res && res.length && Array.isArray(res[0])) { + cb(res[0]); + } else { + cb({error: 'UNEXPECTED_RESPONSE'}); + } }); - }); - }; + }; - ////////////////////////////////////////////////////////////////// - /////////////////////// Store //////////////////////////////////// - ////////////////////////////////////////////////////////////////// - - // Get the metadata for sframe-common-outer - 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 - user: { - name: store.proxy[Constants.displayNameKey] || "", - uid: store.proxy.uid, - avatar: Util.find(store.proxy, ['profile', 'avatar']), - profile: Util.find(store.proxy, ['profile', 'view']), - curvePublic: store.proxy.curvePublic, - }, - // "priv" is not shared with other users but is needed by the apps - priv: { - edPublic: store.proxy.edPublic, - friends: store.proxy.friends || {}, - settings: store.proxy.settings, - thumbnails: disableThumbnails === false - } + Store.initAnonRpc = function (clientId, data, cb) { + if (store.anon_rpc) { return void cb(); } + require([ + '/common/rpc.js', + ], function (Rpc) { + Rpc.createAnonymous(store.network, function (e, call) { + if (e) { return void cb({error: e}); } + store.anon_rpc = call; + cb(); + }); + }); }; - cb(JSON.parse(JSON.stringify(metadata))); - }; - var makePad = function (href, title) { - var now = +new Date(); - return { - href: href, - atime: now, - ctime: now, - title: title || Hash.getDefaultName(Hash.parsePadUrl(href)), + ////////////////////////////////////////////////////////////////// + /////////////////////// Store //////////////////////////////////// + ////////////////////////////////////////////////////////////////// + + // Get the metadata for sframe-common-outer + 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 + user: { + name: store.proxy[Constants.displayNameKey] || "", + uid: store.proxy.uid, + avatar: Util.find(store.proxy, ['profile', 'avatar']), + profile: Util.find(store.proxy, ['profile', 'view']), + curvePublic: store.proxy.curvePublic, + }, + // "priv" is not shared with other users but is needed by the apps + priv: { + edPublic: store.proxy.edPublic, + friends: store.proxy.friends || {}, + settings: store.proxy.settings, + thumbnails: disableThumbnails === false + } + }; + cb(JSON.parse(JSON.stringify(metadata))); }; - }; - 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; } - if (data.expire) { pad.expire = data.expire; } - if (data.password) { pad.password = data.password; } - if (data.channel) { pad.channel = data.channel; } - store.userObject.pushData(pad, function (e, id) { - 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); - }); - }; + var makePad = function (href, title) { + var now = +new Date(); + return { + href: href, + atime: now, + ctime: now, + title: title || Hash.getDefaultName(Hash.parsePadUrl(href)), + }; + }; - var getOwnedPads = function () { - var list = []; - store.userObject.getFiles([store.userObject.FILES_DATA]).forEach(function (id) { - var data = store.userObject.getFileData(id); - var edPublic = store.proxy.edPublic; + 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; } + if (data.expire) { pad.expire = data.expire; } + if (data.password) { pad.password = data.password; } + if (data.channel) { pad.channel = data.channel; } + store.userObject.pushData(pad, function (e, id) { + 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); + }); + }; + + var getOwnedPads = function () { + var list = []; + store.userObject.getFiles([store.userObject.FILES_DATA]).forEach(function (id) { + var data = store.userObject.getFileData(id); + var edPublic = store.proxy.edPublic; - // Push channels owned by someone else or channel that should have expired - // because of the expiration time - if (data.owners && data.owners.length === 1 && data.owners.indexOf(edPublic) !== -1) { - list.push(data.channel); + // Push channels owned by someone else or channel that should have expired + // because of the expiration time + if (data.owners && data.owners.length === 1 && data.owners.indexOf(edPublic) !== -1) { + list.push(data.channel); + } + }); + if (store.proxy.todo) { + // No password for todo + list.push(Hash.hrefToHexChannelId('/todo/#' + store.proxy.todo, null)); } - }); - if (store.proxy.todo) { - // No password for todo - list.push(Hash.hrefToHexChannelId('/todo/#' + store.proxy.todo, null)); - } - if (store.proxy.profile && store.proxy.profile.edit) { - // No password for profile - list.push(Hash.hrefToHexChannelId('/profile/#' + store.proxy.profile.edit, null)); - } - return list; - }; - var removeOwnedPads = function (waitFor) { - // Delete owned pads - var ownedPads = getOwnedPads(); - var sem = Saferphore.create(10); - ownedPads.forEach(function (c) { - var w = waitFor(); - sem.take(function (give) { - Store.removeOwnedChannel(null, c, give(function (obj) { - if (obj && obj.error) { console.error(obj.error); } - w(); - })); + if (store.proxy.profile && store.proxy.profile.edit) { + // No password for profile + list.push(Hash.hrefToHexChannelId('/profile/#' + store.proxy.profile.edit, null)); + } + return list; + }; + var removeOwnedPads = function (waitFor) { + // Delete owned pads + var ownedPads = getOwnedPads(); + var sem = Saferphore.create(10); + ownedPads.forEach(function (c) { + var w = waitFor(); + sem.take(function (give) { + Store.removeOwnedChannel(null, c, give(function (obj) { + if (obj && obj.error) { console.error(obj.error); } + w(); + })); + }); }); - }); - }; + }; - Store.deleteAccount = function (clientId, data, cb) { - var edPublic = store.proxy.edPublic; - // No password for drive - var secret = Hash.getSecrets('drive', storeHash); - Store.anonRpcMsg(clientId, { - msg: 'GET_METADATA', - data: secret.channel - }, function (data) { - var metadata = data[0]; - // Owned drive - if (metadata && metadata.owners && metadata.owners.length === 1 && - metadata.owners.indexOf(edPublic) !== -1) { - nThen(function (waitFor) { - var token = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); - store.proxy[Constants.tokenKey] = token; - postMessage(clientId, "DELETE_ACCOUNT", token, waitFor()); - }).nThen(function (waitFor) { - removeOwnedPads(waitFor); - }).nThen(function (waitFor) { - // Delete Pin Store - store.rpc.removePins(waitFor(function (err) { - if (err) { console.error(err); } - })); - }).nThen(function (waitFor) { - // Delete Drive - Store.removeOwnedChannel(clientId, secret.channel, waitFor()); - }).nThen(function () { - store.network.disconnect(); - cb({ - state: true + Store.deleteAccount = function (clientId, data, cb) { + var edPublic = store.proxy.edPublic; + // No password for drive + var secret = Hash.getSecrets('drive', storeHash); + Store.anonRpcMsg(clientId, { + msg: 'GET_METADATA', + data: secret.channel + }, function (data) { + var metadata = data[0]; + // Owned drive + if (metadata && metadata.owners && metadata.owners.length === 1 && + metadata.owners.indexOf(edPublic) !== -1) { + nThen(function (waitFor) { + var token = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); + store.proxy[Constants.tokenKey] = token; + postMessage(clientId, "DELETE_ACCOUNT", token, waitFor()); + }).nThen(function (waitFor) { + removeOwnedPads(waitFor); + }).nThen(function (waitFor) { + // Delete Pin Store + store.rpc.removePins(waitFor(function (err) { + if (err) { console.error(err); } + })); + }).nThen(function (waitFor) { + // Delete Drive + Store.removeOwnedChannel(clientId, secret.channel, waitFor()); + }).nThen(function () { + store.network.disconnect(); + cb({ + state: true + }); }); - }); - return; - } + return; + } - // Not owned drive - var toSign = { - intent: 'Please delete my account.' - }; - toSign.drive = secret.channel; - toSign.edPublic = edPublic; - var signKey = Crypto.Nacl.util.decodeBase64(store.proxy.edPrivate); - var proof = Crypto.Nacl.sign.detached(Crypto.Nacl.util.decodeUTF8(Sortify(toSign)), signKey); + // Not owned drive + var toSign = { + intent: 'Please delete my account.' + }; + toSign.drive = secret.channel; + toSign.edPublic = edPublic; + var signKey = Crypto.Nacl.util.decodeBase64(store.proxy.edPrivate); + var proof = Crypto.Nacl.sign.detached(Crypto.Nacl.util.decodeUTF8(Sortify(toSign)), signKey); - var check = Crypto.Nacl.sign.detached.verify(Crypto.Nacl.util.decodeUTF8(Sortify(toSign)), - proof, - Crypto.Nacl.util.decodeBase64(edPublic)); + var check = Crypto.Nacl.sign.detached.verify(Crypto.Nacl.util.decodeUTF8(Sortify(toSign)), + proof, + Crypto.Nacl.util.decodeBase64(edPublic)); - if (!check) { console.error('signed message failed verification'); } + if (!check) { console.error('signed message failed verification'); } - var proofTxt = Crypto.Nacl.util.encodeBase64(proof); - cb({ - proof: proofTxt, - toSign: JSON.parse(Sortify(toSign)) + var proofTxt = Crypto.Nacl.util.encodeBase64(proof); + cb({ + proof: proofTxt, + toSign: JSON.parse(Sortify(toSign)) + }); }); - }); - }; + }; - /** - * add a "What is CryptPad?" pad in the drive - * data - * - driveReadme - * - driveReadmeTitle - */ - Store.createReadme = function (clientId, data, cb) { - require(['/common/cryptget.js'], function (Crypt) { - var hash = Hash.createRandomHash('pad'); - Crypt.put(hash, data.driveReadme, function (e) { - if (e) { - return void cb({ error: "Error while creating the default pad:"+ e}); - } - var href = '/pad/#' + hash; - var channel = Hash.hrefToHexChannelId(href, null); - var fileData = { - href: href, - channel: channel, - title: data.driveReadmeTitle, - }; - Store.addPad(clientId, fileData, cb); + /** + * add a "What is CryptPad?" pad in the drive + * data + * - driveReadme + * - driveReadmeTitle + */ + Store.createReadme = function (clientId, data, cb) { + require(['/common/cryptget.js'], function (Crypt) { + var hash = Hash.createRandomHash('pad'); + Crypt.put(hash, data.driveReadme, function (e) { + if (e) { + return void cb({ error: "Error while creating the default pad:"+ e}); + } + var href = '/pad/#' + hash; + var channel = Hash.hrefToHexChannelId(href, null); + var fileData = { + href: href, + channel: channel, + title: data.driveReadmeTitle, + }; + Store.addPad(clientId, fileData, cb); + }); }); - }); - }; + }; - /** - * Merge the anonymous drive into the user drive at registration - * data - * - anonHash - */ - Store.migrateAnonDrive = function (clientId, data, cb) { - require(['/common/mergeDrive.js'], function (Merge) { - var hash = data.anonHash; - Merge.anonDriveIntoUser(store, hash, cb); - }); - }; + /** + * Merge the anonymous drive into the user drive at registration + * data + * - anonHash + */ + Store.migrateAnonDrive = function (clientId, data, cb) { + require(['/common/mergeDrive.js'], function (Merge) { + var hash = data.anonHash; + Merge.anonDriveIntoUser(store, hash, cb); + }); + }; - var getAttributeObject = function (attr) { - if (typeof attr === "string") { - console.error('DEPRECATED: use setAttribute with an array, not a string'); + var getAttributeObject = function (attr) { + if (typeof attr === "string") { + console.error('DEPRECATED: use setAttribute with an array, not a string'); + return { + path: ['settings'], + obj: store.proxy.settings, + key: attr + }; + } + if (!Array.isArray(attr)) { return void console.error("Attribute must be string or array"); } + if (attr.length === 0) { return void console.error("Attribute can't be empty"); } + var obj = store.proxy.settings; + attr.forEach(function (el, i) { + if (i === attr.length-1) { return; } + if (!obj[el]) { + obj[el] = {}; + } + else if (typeof obj[el] !== "object") { return void console.error("Wrong attribute"); } + obj = obj[el]; + }); return { - path: ['settings'], - obj: store.proxy.settings, - key: attr + path: ['settings'].concat(attr), + obj: obj, + key: attr[attr.length-1] }; - } - if (!Array.isArray(attr)) { return void console.error("Attribute must be string or array"); } - if (attr.length === 0) { return void console.error("Attribute can't be empty"); } - var obj = store.proxy.settings; - attr.forEach(function (el, i) { - if (i === attr.length-1) { return; } - if (!obj[el]) { - obj[el] = {}; - } - else if (typeof obj[el] !== "object") { return void console.error("Wrong attribute"); } - 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 (clientId, value, cb) { - store.proxy[Constants.displayNameKey] = value; - broadcast([clientId], "UPDATE_METADATA"); - onSync(cb); - }; + // Set the display name (username) in the proxy + 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 (clientId, data, cb) { - nThen(function (waitFor) { - removeOwnedPads(waitFor); - }).nThen(function () { - store.proxy.drive = store.fo.getStructure(); - sendDriveEvent('DRIVE_CHANGE', { - path: ['drive', 'filesData'] - }, clientId); + // Reset the drive part of the userObject (from settings) + 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); + }); + }; + + /** + * Settings & pad attributes + * data + * - href (String) + * - attr (Array) + * - value (String) + */ + 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 (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 (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 (clientId, data, cb) { + var object; + try { + object = getAttributeObject(data.attr); + } catch (e) { return void cb({error: e}); } + cb(object.obj[object.key]); + }; + + // Tags + Store.listAllTags = function (clientId, data, cb) { + cb(store.userObject.getTagsList()); + }; + + // Templates + Store.getTemplates = function (clientId, data, cb) { + var templateFiles = store.userObject.getFiles(['template']); + var res = []; + templateFiles.forEach(function (f) { + var data = store.userObject.getFileData(f); + res.push(JSON.parse(JSON.stringify(data))); + }); + cb(res); + }; + 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 + if (err) { return; } + var used = typeof data === "number" ? ++data : 1; + store.userObject.setPadAttribute(href, 'used', used); + }); + }; - /** - * Settings & pad attributes - * data - * - href (String) - * - attr (Array) - * - value (String) - */ - Store.setPadAttribute = function (clientId, data, cb) { - store.userObject.setPadAttribute(data.href, data.attr, data.value, function () { + // Pads + 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.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 (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 (clientId, data, cb) { - var object; - try { - object = getAttributeObject(data.attr); - } catch (e) { return void cb({error: e}); } - cb(object.obj[object.key]); - }; + }; + Store.setPadTitle = function (clientId, data, cb) { + var title = data.title; + var href = data.href; + var channel = data.channel; + var p = Hash.parsePadUrl(href); + var h = p.hashData; - // Tags - Store.listAllTags = function (clientId, data, cb) { - cb(store.userObject.getTagsList()); - }; + if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); } - // Templates - Store.getTemplates = function (clientId, data, cb) { - var templateFiles = store.userObject.getFiles(['template']); - var res = []; - templateFiles.forEach(function (f) { - var data = store.userObject.getFileData(f); - res.push(JSON.parse(JSON.stringify(data))); - }); - cb(res); - }; - 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 - if (err) { return; } - var used = typeof data === "number" ? ++data : 1; - store.userObject.setPadAttribute(href, 'used', used); - }); - }; + var channelData = Store.channels && Store.channels[channel]; - // Pads - 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 (clientId, data, cb) { - var title = data.title; - var href = data.href; - var channel = data.channel; - var p = Hash.parsePadUrl(href); - var h = p.hashData; - - if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); } - - var channelData = Store.channels && Store.channels[channel]; - - var owners; - if (channelData && channelData.wc && channel === channelData.wc.id) { - owners = channelData.data.owners || undefined; - } - - var expire; - if (channelData && channelData.wc && channel === channelData.wc.id) { - expire = +channelData.data.expire || undefined; - } - - var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {}; - var isStronger; - - // If we don't find the new channel in our existing pads, we'll have to add the pads - // to filesData - var contains; - - // Update all pads that use the same channel but with a weaker hash - // Edit > Edit (present) > View > View (present) - for (var id in allPads) { - var pad = allPads[id]; - if (!pad.href) { continue; } - - var p2 = Hash.parsePadUrl(pad.href); - var h2 = p2.hashData; - - // Different types, proceed to the next one - // No hash data: corrupted pad? - if (p.type !== p2.type || !h2) { continue; } - // Different channel: continue - if (pad.channel !== channel) { continue; } - - var shouldUpdate = p.hash.replace(/\/$/, '') === p2.hash.replace(/\/$/, ''); - - // If the hash is different but represents the same channel, check if weaker or stronger - if (!shouldUpdate && h.version !== 0) { - // We had view & now we have edit, update - if (h2.mode === 'view' && h.mode === 'edit') { shouldUpdate = true; } - // Same mode and we had present URL, update - else if (h.mode === h2.mode && h2.present) { shouldUpdate = true; } - // If we're here it means we have a weaker URL: - // update the date but keep the existing hash - else { - pad.atime = +new Date(); - contains = true; - continue; - } + var owners; + if (channelData && channelData.wc && channel === channelData.wc.id) { + owners = channelData.data.owners || undefined; } - if (shouldUpdate) { - contains = true; - pad.atime = +new Date(); - pad.title = title; - if (owners || h.type !== "file") { - // OWNED_FILES - // Never remove owner for files - pad.owners = owners; - } - pad.expire = expire; - - // If the href is different, it means we have a stronger one - if (href !== pad.href) { isStronger = true; } - pad.href = href; + var expire; + if (channelData && channelData.wc && channel === channelData.wc.id) { + expire = +channelData.data.expire || undefined; } - } - if (isStronger) { - // If we have a stronger url, remove the possible weaker from the trash. - // If all of the weaker ones were in the trash, add the stronger to ROOT - store.userObject.restoreHref(href); - } + var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {}; + var isStronger; + + // If we don't find the new channel in our existing pads, we'll have to add the pads + // to filesData + var contains; + + // Update all pads that use the same channel but with a weaker hash + // Edit > Edit (present) > View > View (present) + for (var id in allPads) { + var pad = allPads[id]; + if (!pad.href) { continue; } + + var p2 = Hash.parsePadUrl(pad.href); + var h2 = p2.hashData; + + // Different types, proceed to the next one + // No hash data: corrupted pad? + if (p.type !== p2.type || !h2) { continue; } + // Different channel: continue + if (pad.channel !== channel) { continue; } + + var shouldUpdate = p.hash.replace(/\/$/, '') === p2.hash.replace(/\/$/, ''); + + // If the hash is different but represents the same channel, check if weaker or stronger + if (!shouldUpdate && h.version !== 0) { + // We had view & now we have edit, update + if (h2.mode === 'view' && h.mode === 'edit') { shouldUpdate = true; } + // Same mode and we had present URL, update + else if (h.mode === h2.mode && h2.present) { shouldUpdate = true; } + // If we're here it means we have a weaker URL: + // update the date but keep the existing hash + else { + pad.atime = +new Date(); + contains = true; + continue; + } + } - // Add the pad if it does not exist in our drive - if (!contains) { - Store.addPad(clientId, { - href: href, - channel: channel, - title: title, - owners: owners, - expire: expire, - password: data.password, - path: data.path - }, cb); - return; - } else { - sendDriveEvent('DRIVE_CHANGE', { - path: ['drive', UserObject.FILES_DATA] - }, clientId); - } - onSync(cb); - }; + if (shouldUpdate) { + contains = true; + pad.atime = +new Date(); + pad.title = title; + if (owners || h.type !== "file") { + // OWNED_FILES + // Never remove owner for files + pad.owners = owners; + } + pad.expire = expire; - // Filepicker app - Store.getSecureFilesList = function (clientId, query, cb) { - var list = {}; - var hashes = []; - var types = query.types; - var where = query.where; - var filter = query.filter || {}; - var isFiltered = function (type, data) { - var filtered; - var fType = filter.fileType || []; - if (type === 'file' && fType.length) { - if (!data.fileType) { return true; } - filtered = !fType.some(function (t) { - return data.fileType.indexOf(t) === 0; - }); + // If the href is different, it means we have a stronger one + if (href !== pad.href) { isStronger = true; } + pad.href = href; + } } - return filtered; - }; - store.userObject.getFiles(where).forEach(function (id) { - var data = store.userObject.getFileData(id); - var parsed = Hash.parsePadUrl(data.href); - if ((!types || types.length === 0 || types.indexOf(parsed.type) !== -1) && - hashes.indexOf(parsed.hash) === -1 && - !isFiltered(parsed.type, data)) { - hashes.push(parsed.hash); - list[id] = data; + + if (isStronger) { + // If we have a stronger url, remove the possible weaker from the trash. + // If all of the weaker ones were in the trash, add the stronger to ROOT + store.userObject.restoreHref(href); } - }); - cb(list); - }; - Store.getPadData = function (clientId, id, cb) { - cb(store.userObject.getFileData(id)); - }; + // Add the pad if it does not exist in our drive + if (!contains) { + Store.addPad(clientId, { + href: href, + channel: channel, + title: title, + owners: owners, + expire: expire, + password: data.password, + path: data.path + }, cb); + return; + } else { + sendDriveEvent('DRIVE_CHANGE', { + path: ['drive', UserObject.FILES_DATA] + }, clientId); + } + onSync(cb); + }; - // Messaging (manage friends from the userlist) - var getMessagingCfg = function (clientId) { - return { - proxy: store.proxy, - realtime: store.realtime, - network: store.network, - updateMetadata: function () { - postMessage(clientId, "UPDATE_METADATA"); - }, - pinPads: function (data, cb) { Store.pinPads(null, data, cb); }, - friendComplete: function (data) { - postMessage(clientId, "EV_FRIEND_COMPLETE", data); - }, - friendRequest: function (data, cb) { - postMessage(clientId, "Q_FRIEND_REQUEST", data, cb); - }, + // Filepicker app + Store.getSecureFilesList = function (clientId, query, cb) { + var list = {}; + var hashes = []; + var types = query.types; + var where = query.where; + var filter = query.filter || {}; + var isFiltered = function (type, data) { + var filtered; + var fType = filter.fileType || []; + if (type === 'file' && fType.length) { + if (!data.fileType) { return true; } + filtered = !fType.some(function (t) { + return data.fileType.indexOf(t) === 0; + }); + } + return filtered; + }; + store.userObject.getFiles(where).forEach(function (id) { + var data = store.userObject.getFileData(id); + var parsed = Hash.parsePadUrl(data.href); + if ((!types || types.length === 0 || types.indexOf(parsed.type) !== -1) && + hashes.indexOf(parsed.hash) === -1 && + !isFiltered(parsed.type, data)) { + hashes.push(parsed.hash); + list[id] = data; + } + }); + cb(list); + }; + Store.getPadData = function (clientId, id, cb) { + cb(store.userObject.getFileData(id)); }; - }; - Store.inviteFromUserlist = function (clientId, data, cb) { - var messagingCfg = getMessagingCfg(clientId); - Messaging.inviteFromUserlist(messagingCfg, data, cb); - }; - Store.addDirectMessageHandlers = function (clientId, data) { - var messagingCfg = getMessagingCfg(clientId); - Messaging.addDirectMessageHandler(messagingCfg, data.href); - }; - // Messenger - // Get hashes for the share button - Store.getStrongerHash = function (clientId, data, cb) { - var allPads = Util.find(store.proxy, ['drive', 'filesData']) || {}; + // Messaging (manage friends from the userlist) + var getMessagingCfg = function (clientId) { + return { + proxy: store.proxy, + realtime: store.realtime, + network: store.network, + updateMetadata: function () { + postMessage(clientId, "UPDATE_METADATA"); + }, + pinPads: function (data, cb) { Store.pinPads(null, data, cb); }, + friendComplete: function (data) { + postMessage(clientId, "EV_FRIEND_COMPLETE", data); + }, + friendRequest: function (data, cb) { + postMessage(clientId, "Q_FRIEND_REQUEST", data, cb); + }, + }; + }; + Store.inviteFromUserlist = function (clientId, data, cb) { + var messagingCfg = getMessagingCfg(clientId); + Messaging.inviteFromUserlist(messagingCfg, data, cb); + }; + Store.addDirectMessageHandlers = function (clientId, data) { + var messagingCfg = getMessagingCfg(clientId); + Messaging.addDirectMessageHandler(messagingCfg, data.href); + }; - // If we have a stronger version in drive, add it and add a redirect button - var stronger = Hash.findStronger(data.href, data.channel, allPads); - if (stronger) { - var parsed2 = Hash.parsePadUrl(stronger.href); - return void cb(parsed2.hash); - } - cb(); - }; + // Messenger - Store.messenger = { - getFriendList: function (data, cb) { - store.messenger.getFriendList(function (e, keys) { - cb({ - error: e, - data: keys, - }); - }); - }, - getMyInfo: function (data, cb) { - store.messenger.getMyInfo(function (e, info) { - cb({ - error: e, - data: info, + // Get hashes for the share button + 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 + var stronger = Hash.findStronger(data.href, data.channel, allPads); + if (stronger) { + var parsed2 = Hash.parsePadUrl(stronger.href); + return void cb(parsed2.hash); + } + cb(); + }; + + Store.messenger = { + getFriendList: function (data, cb) { + store.messenger.getFriendList(function (e, keys) { + cb({ + error: e, + data: keys, + }); }); - }); - }, - getFriendInfo: function (data, cb) { - store.messenger.getFriendInfo(data, function (e, info) { - cb({ - error: e, - data: info, + }, + getMyInfo: function (data, cb) { + store.messenger.getMyInfo(function (e, info) { + cb({ + error: e, + data: info, + }); }); - }); - }, - removeFriend: function (data, cb) { - store.messenger.removeFriend(data, function (e, info) { - cb({ - error: e, - data: info, + }, + getFriendInfo: function (data, cb) { + store.messenger.getFriendInfo(data, function (e, info) { + cb({ + error: e, + data: info, + }); }); - }); - }, - openFriendChannel: function (data, cb) { - store.messenger.openFriendChannel(data, function (e) { - cb({ error: e, }); - }); - }, - getFriendStatus: function (data, cb) { - store.messenger.getStatus(data, function (e, online) { - cb({ - error: e, - data: online, + }, + removeFriend: function (data, cb) { + store.messenger.removeFriend(data, function (e, info) { + cb({ + error: e, + data: info, + }); }); - }); - }, - getMoreHistory: function (data, cb) { - store.messenger.getMoreHistory(data.curvePublic, data.sig, data.count, function (e, history) { - cb({ - error: e, - data: history, + }, + openFriendChannel: function (data, cb) { + store.messenger.openFriendChannel(data, function (e) { + cb({ error: e, }); }); - }); - }, - sendMessage: function (data, cb) { - store.messenger.sendMessage(data.curvePublic, data.content, function (e) { - cb({ - error: e, + }, + getFriendStatus: function (data, cb) { + store.messenger.getStatus(data, function (e, online) { + cb({ + error: e, + data: online, + }); }); - }); - }, - setChannelHead: function (data, cb) { - store.messenger.setChannelHead(data.curvePublic, data.sig, function (e) { - cb({ - error: e + }, + getMoreHistory: function (data, cb) { + store.messenger.getMoreHistory(data.curvePublic, data.sig, data.count, function (e, history) { + cb({ + error: e, + data: history, + }); }); - }); - } - }; - - ////////////////////////////////////////////////////////////////// - /////////////////////// PAD ////////////////////////////////////// - ////////////////////////////////////////////////////////////////// - - var channels = Store.channels = {}; - - Store.joinPad = function (clientId, data) { - 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); + }, + sendMessage: function (data, cb) { + store.messenger.sendMessage(data.curvePublic, data.content, function (e) { + cb({ + error: e, + }); }); }, - history: [], - pushHistory: function (msg, isCp) { - if (isCp) { - channel.history.push('cp|' + msg); - var i; - for (i = channel.history.length - 2; i > 0; i--) { - if (/^cp\|/.test(channel.history[i])) { break; } + setChannelHead: function (data, cb) { + store.messenger.setChannelHead(data.curvePublic, data.sig, function (e) { + cb({ + error: e + }); + }); + } + }; + + ////////////////////////////////////////////////////////////////// + /////////////////////// PAD ////////////////////////////////////// + ////////////////////////////////////////////////////////////////// + + var channels = Store.channels = {}; + + Store.joinPad = function (clientId, data) { + 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; + 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 = channel.history.slice(i); - return; + channel.history.push(msg); } - channel.history.push(msg); + }; + if (channel.clients.indexOf(clientId) === -1) { + channel.clients.push(clientId); } - }; - if (channel.clients.indexOf(clientId) === -1) { - channel.clients.push(clientId); - } - if (!isNew && channel.wc) { - postMessage(clientId, "PAD_CONNECT", { - myID: channel.wc.myID, - id: channel.wc.id, - members: channel.wc.members - }); - 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 + if (!isNew && channel.wc) { + postMessage(clientId, "PAD_CONNECT", { + myID: channel.wc.myID, + id: channel.wc.id, + members: channel.wc.members }); - }); - postMessage(clientId, "PAD_READY"); - - return; - } - var conf = { - onReady: function (padData) { - channel.data = padData || {}; - postMessage(clientId, "PAD_READY"); - }, - onMessage: function (user, m, validateKey, isCp) { - channel.pushHistory(m, isCp); - channel.bcast("PAD_MESSAGE", { - user: user, - msg: m, - validateKey: validateKey + channel.wc.members.forEach(function (m) { + postMessage(clientId, "PAD_JOIN", m); }); - }, - onJoin: function (m) { - channel.bcast("PAD_JOIN", m); - }, - onLeave: function (m) { - channel.bcast("PAD_LEAVE", m); - }, - onDisconnect: function () { - channel.bcast("PAD_DISCONNECT"); - }, - onError: function (err) { - 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, - onConnect: function (wc, 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, + channel.history.forEach(function (msg) { + postMessage(clientId, "PAD_MESSAGE", { msg: CpNfWorker.removeCp(msg), + user: channel.wc.myID, validateKey: channel.data.validateKey - }, cId); - }; - channel.wc = wc; - channel.queue.forEach(function (data) { - channel.sendMessage(data.message, clientId); - }); - channel.bcast("PAD_CONNECT", { - myID: wc.myID, - id: wc.id, - members: wc.members + }); }); - } - }; - CpNfWorker.start(conf); - }; - Store.sendPadMsg = function (clientId, data, cb) { - var msg = data.msg; - var channel = channels[data.channel]; - if (!channel) { - return; } - if (!channel.wc) { - channel.queue.push(msg); - return void cb(); - } - channel.sendMessage(msg, clientId, cb); - }; + postMessage(clientId, "PAD_READY"); - // GET_FULL_HISTORY from sframe-common-outer - Store.getFullHistory = function (clientId, data, cb) { - var network = store.network; - var hkn = network.historyKeeper; - //var crypto = Crypto.createEncryptor(data.keys); - // Get the history messages and send them to the iframe - var parse = function (msg) { - try { - return JSON.parse(msg); - } catch (e) { - return null; - } - }; - var msgs = []; - var onMsg = function (msg) { - var parsed = parse(msg); - if (parsed[0] === 'FULL_HISTORY_END') { - cb(msgs); - return; - } - if (parsed[0] !== 'FULL_HISTORY') { return; } - if (parsed[1] && parsed[1].validateKey) { // First message return; } - if (parsed[1][3] !== data.channel) { return; } - msg = parsed[1][4]; - if (msg) { - msg = msg.replace(/cp\|(([A-Za-z0-9+\/=]+)\|)?/, ''); - //var decryptedMsg = crypto.decrypt(msg, true); - msgs.push(msg); + var conf = { + onReady: function (padData) { + channel.data = padData || {}; + postMessage(clientId, "PAD_READY"); + }, + onMessage: function (user, m, validateKey, isCp) { + channel.pushHistory(m, isCp); + channel.bcast("PAD_MESSAGE", { + user: user, + msg: m, + validateKey: validateKey + }); + }, + onJoin: function (m) { + channel.bcast("PAD_JOIN", m); + }, + onLeave: function (m) { + channel.bcast("PAD_LEAVE", m); + }, + onDisconnect: function () { + channel.bcast("PAD_DISCONNECT"); + }, + onError: function (err) { + 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, + onConnect: function (wc, 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) { + channel.sendMessage(data.message, clientId); + }); + channel.bcast("PAD_CONNECT", { + myID: wc.myID, + id: wc.id, + members: wc.members + }); + } + }; + CpNfWorker.start(conf); + }; + Store.sendPadMsg = function (clientId, data, cb) { + var msg = data.msg; + var channel = channels[data.channel]; + if (!channel) { + return; } + if (!channel.wc) { + channel.queue.push(msg); + return void cb(); } + channel.sendMessage(msg, clientId, cb); }; - network.on('message', onMsg); - network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', data.channel, data.validateKey])); - }; - // Drive - 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, cb2); break; - case 'restore': - store.userObject.restore(data.path, cb2); break; - case 'addFolder': - store.userObject.addFolder(data.path, data.name, cb2); break; - case 'delete': - store.userObject.delete(data.paths, cb2, data.nocheck, data.isOwnPadRemoved); break; - case 'emptyTrash': - store.userObject.emptyTrash(cb2); break; - case 'rename': - store.userObject.rename(data.path, data.newName, cb2); break; - default: - cb(); - } - }; + // GET_FULL_HISTORY from sframe-common-outer + Store.getFullHistory = function (clientId, data, cb) { + var network = store.network; + var hkn = network.historyKeeper; + //var crypto = Crypto.createEncryptor(data.keys); + // Get the history messages and send them to the iframe + var parse = function (msg) { + try { + return JSON.parse(msg); + } catch (e) { + return null; + } + }; + var msgs = []; + var onMsg = function (msg) { + var parsed = parse(msg); + if (parsed[0] === 'FULL_HISTORY_END') { + cb(msgs); + return; + } + if (parsed[0] !== 'FULL_HISTORY') { return; } + if (parsed[1] && parsed[1].validateKey) { // First message + return; + } + if (parsed[1][3] !== data.channel) { return; } + msg = parsed[1][4]; + if (msg) { + msg = msg.replace(/cp\|(([A-Za-z0-9+\/=]+)\|)?/, ''); + //var decryptedMsg = crypto.decrypt(msg, true); + msgs.push(msg); + } + }; + network.on('message', onMsg); + network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', data.channel, data.validateKey])); + }; - // Clients management - var driveEventClients = []; - var messengerEventClients = []; + // Drive + 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, cb2); break; + case 'restore': + store.userObject.restore(data.path, cb2); break; + case 'addFolder': + store.userObject.addFolder(data.path, data.name, cb2); break; + case 'delete': + store.userObject.delete(data.paths, cb2, data.nocheck, data.isOwnPadRemoved); break; + case 'emptyTrash': + store.userObject.emptyTrash(cb2); break; + case 'rename': + store.userObject.rename(data.path, data.newName, cb2); break; + default: + cb(); + } + }; - var dropChannel = function (chanId) { - if (!Store.channels[chanId]) { return; } + // Clients management + var driveEventClients = []; + var messengerEventClients = []; - if (Store.channels[chanId].wc) { - Store.channels[chanId].wc.leave(''); - } - delete Store.channels[chanId]; - }; - 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); + var dropChannel = function (chanId) { + if (!Store.channels[chanId]) { return; } + + if (Store.channels[chanId].wc) { + Store.channels[chanId].wc.leave(''); } - if (Store.channels[chanId].clients.length === 0) { - dropChannel(chanId); + delete Store.channels[chanId]; + }; + 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); + } + if (Store.channels[chanId].clients.length === 0) { + dropChannel(chanId); + } + }); + }; - // Special events + // Special events - var driveEventInit = false; - sendDriveEvent = function (q, data, sender) { - 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 - }); + var driveEventInit = false; + sendDriveEvent = function (q, data, sender) { + driveEventClients.forEach(function (cId) { + if (cId === sender) { return; } + postMessage(cId, q, data); }); - store.proxy.on('remove', [], function (o, p) { - sendDriveEvent(clientId, 'DRIVE_REMOVE', { - old: o, - path: p + }; + 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 + }); }); - }); - driveEventInit = true; - } - }; + 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); + var messengerEventInit = false; + var sendMessengerEvent = function (q, data) { + messengerEventClients.forEach(function (cId) { + postMessage(cId, q, data); }); - messenger.on('join', function (curvePublic, channel) { - sendMessengerEvent('CONTACTS_JOIN', { - curvePublic: curvePublic, - channel: channel, + }; + 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('leave', function (curvePublic, channel) { - sendMessengerEvent('CONTACTS_LEAVE', { - curvePublic: curvePublic, - channel: channel, + messenger.on('join', function (curvePublic, channel) { + sendMessengerEvent('CONTACTS_JOIN', { + curvePublic: curvePublic, + channel: channel, + }); }); - }); - messenger.on('update', function (info, curvePublic) { - sendMessengerEvent('CONTACTS_UPDATE', { - curvePublic: curvePublic, - info: info, + messenger.on('leave', function (curvePublic, channel) { + sendMessengerEvent('CONTACTS_LEAVE', { + curvePublic: curvePublic, + channel: channel, + }); }); - }); - messenger.on('friend', function (curvePublic) { - sendMessengerEvent('CONTACTS_FRIEND', { - curvePublic: curvePublic, + messenger.on('update', function (info, curvePublic) { + sendMessengerEvent('CONTACTS_UPDATE', { + curvePublic: curvePublic, + info: info, + }); }); - }); - messenger.on('unfriend', function (curvePublic) { - sendMessengerEvent('CONTACTS_UNFRIEND', { - curvePublic: curvePublic, + messenger.on('friend', function (curvePublic) { + sendMessengerEvent('CONTACTS_FRIEND', { + curvePublic: curvePublic, + }); }); - }); - messengerEventInit = true; - } - }; + messenger.on('unfriend', function (curvePublic) { + sendMessengerEvent('CONTACTS_UNFRIEND', { + curvePublic: curvePublic, + }); + }); + messengerEventInit = true; + } + }; - ////////////////////////////////////////////////////////////////// - /////////////////////// Init ///////////////////////////////////// - ////////////////////////////////////////////////////////////////// - - var onReady = function (clientId, returned, cb) { - var proxy = store.proxy; - var userObject = store.userObject = UserObject.init(proxy.drive, { - 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) { - // broadcast to all drive apps - sendDriveEvent("DRIVE_LOG", msg); - } - }); - nThen(function (waitFor) { - postMessage(clientId, 'LOADING_DRIVE', { - state: 2 + ////////////////////////////////////////////////////////////////// + /////////////////////// Init ///////////////////////////////////// + ////////////////////////////////////////////////////////////////// + + var onReady = function (clientId, returned, cb) { + var proxy = store.proxy; + var userObject = store.userObject = UserObject.init(proxy.drive, { + 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) { + // broadcast to all drive apps + sendDriveEvent("DRIVE_LOG", msg); + } }); - userObject.migrate(waitFor()); - }).nThen(function (waitFor) { - Migrate(proxy, waitFor(), function (version, progress) { + nThen(function (waitFor) { postMessage(clientId, 'LOADING_DRIVE', { - state: 2, - progress: progress + state: 2 }); - }); - }).nThen(function () { - postMessage(clientId, 'LOADING_DRIVE', { - state: 3 - }); - userObject.fixFiles(); + userObject.migrate(waitFor()); + }).nThen(function (waitFor) { + Migrate(proxy, waitFor(), function (version, progress) { + postMessage(clientId, 'LOADING_DRIVE', { + state: 2, + progress: progress + }); + }); + }).nThen(function () { + postMessage(clientId, 'LOADING_DRIVE', { + state: 3 + }); + userObject.fixFiles(); - var requestLogin = function () { - broadcast([], "REQUEST_LOGIN"); - }; + var requestLogin = function () { + broadcast([], "REQUEST_LOGIN"); + }; - if (store.loggedIn) { - /* This isn't truly secure, since anyone who can read the user's object can - set their local loginToken to match that in the object. However, it exposes - a UI that will work most of the time. */ + if (store.loggedIn) { + /* This isn't truly secure, since anyone who can read the user's object can + set their local loginToken to match that in the object. However, it exposes + a UI that will work most of the time. */ - // every user object should have a persistent, random number - if (typeof(proxy.loginToken) !== 'number') { - proxy[Constants.tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); - } - returned[Constants.tokenKey] = proxy[Constants.tokenKey]; + // every user object should have a persistent, random number + if (typeof(proxy.loginToken) !== 'number') { + proxy[Constants.tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); + } + returned[Constants.tokenKey] = proxy[Constants.tokenKey]; - if (store.data.localToken && store.data.localToken !== proxy[Constants.tokenKey]) { - // the local number doesn't match that in - // the user object, request that they reauthenticate. - return void requestLogin(); + if (store.data.localToken && store.data.localToken !== proxy[Constants.tokenKey]) { + // the local number doesn't match that in + // the user object, request that they reauthenticate. + return void requestLogin(); + } } - } - if (!proxy.settings || !proxy.settings.general || - typeof(proxy.settings.general.allowUserFeedback) !== 'boolean') { - proxy.settings = proxy.settings || {}; - proxy.settings.general = proxy.settings.general || {}; - proxy.settings.general.allowUserFeedback = true; - } - returned.feedback = proxy.settings.general.allowUserFeedback; + if (!proxy.settings || !proxy.settings.general || + typeof(proxy.settings.general.allowUserFeedback) !== 'boolean') { + proxy.settings = proxy.settings || {}; + proxy.settings.general = proxy.settings.general || {}; + proxy.settings.general.allowUserFeedback = true; + } + returned.feedback = proxy.settings.general.allowUserFeedback; - if (typeof(cb) === 'function') { cb(returned); } + if (typeof(cb) === 'function') { cb(returned); } - if (typeof(proxy.uid) !== 'string' || proxy.uid.length !== 32) { - // even anonymous users should have a persistent, unique-ish id - console.log('generating a persistent identifier'); - proxy.uid = Hash.createChannelId(); - } + if (typeof(proxy.uid) !== 'string' || proxy.uid.length !== 32) { + // even anonymous users should have a persistent, unique-ish id + console.log('generating a persistent identifier'); + proxy.uid = Hash.createChannelId(); + } - // if the user is logged in, but does not have signing keys... - if (store.loggedIn && (!Store.hasSigningKeys() || - !Store.hasCurveKeys())) { - return void requestLogin(); - } + // if the user is logged in, but does not have signing keys... + if (store.loggedIn && (!Store.hasSigningKeys() || + !Store.hasCurveKeys())) { + return void requestLogin(); + } - proxy.on('change', [Constants.displayNameKey], function (o, n) { - if (typeof(n) !== "string") { return; } - broadcast([], "UPDATE_METADATA"); - }); - proxy.on('change', ['profile'], function () { - // Trigger userlist update when the avatar has changed - broadcast([], "UPDATE_METADATA"); + proxy.on('change', [Constants.displayNameKey], function (o, n) { + if (typeof(n) !== "string") { return; } + broadcast([], "UPDATE_METADATA"); + }); + proxy.on('change', ['profile'], function () { + // Trigger userlist update when the avatar has changed + broadcast([], "UPDATE_METADATA"); + }); + proxy.on('change', ['friends'], function () { + // Trigger userlist update when the friendlist has changed + broadcast([], "UPDATE_METADATA"); + }); + proxy.on('change', ['settings'], function () { + broadcast([], "UPDATE_METADATA"); + }); + proxy.on('change', [Constants.tokenKey], function () { + broadcast([], "UPDATE_TOKEN", { token: proxy[Constants.tokenKey] }); + }); }); - proxy.on('change', ['friends'], function () { - // Trigger userlist update when the friendlist has changed - broadcast([], "UPDATE_METADATA"); + }; + + var connect = function (clientId, data, cb) { + var hash = data.userHash || data.anonHash || Hash.createRandomHash('drive'); + storeHash = hash; + if (!hash) { + throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...'); + } + // No password for drive + var secret = Hash.getSecrets('drive', hash); + var listmapConfig = { + data: {}, + websocketURL: NetConfig.getWebsocketURL(), + channel: secret.channel, + readOnly: false, + validateKey: secret.keys.validateKey || undefined, + crypto: Crypto.createEncryptor(secret.keys), + userName: 'fs', + logLevel: 1, + ChainPad: ChainPad, + classic: true, + }; + var rt = window.rt = Listmap.create(listmapConfig); + store.proxy = rt.proxy; + store.loggedIn = typeof(data.userHash) !== "undefined"; + + var returned = {}; + rt.proxy.on('create', function (info) { + store.realtime = info.realtime; + store.network = info.network; + if (!data.userHash) { + returned.anonHash = Hash.getEditHashFromKeys(secret); + } + }).on('ready', function () { + if (store.userObject) { return; } // the store is already ready, it is a reconnection + if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; } + var drive = rt.proxy.drive; + // Creating a new anon drive: import anon pads from localStorage + if ((!drive[Constants.oldStorageKey] || !Array.isArray(drive[Constants.oldStorageKey])) + && !drive['filesData']) { + drive[Constants.oldStorageKey] = []; + } + postMessage(clientId, 'LOADING_DRIVE', { state: 1 }); + // Drive already exist: return the existing drive, don't load data from legacy store + onReady(clientId, returned, cb); + }) + .on('change', ['drive', 'migrate'], function () { + var path = arguments[2]; + var value = arguments[1]; + if (path[0] === 'drive' && path[1] === "migrate" && value === 1) { + rt.network.disconnect(); + rt.realtime.abort(); + broadcast([], 'NETWORK_DISCONNECT'); + } }); - proxy.on('change', ['settings'], function () { - broadcast([], "UPDATE_METADATA"); + + rt.proxy.on('disconnect', function () { + broadcast([], 'NETWORK_DISCONNECT'); }); - proxy.on('change', [Constants.tokenKey], function () { - broadcast([], "UPDATE_TOKEN", { token: proxy[Constants.tokenKey] }); + rt.proxy.on('reconnect', function (info) { + broadcast([], 'NETWORK_RECONNECT', {myId: info.myId}); }); - }); - }; + }; - var connect = function (clientId, data, cb) { - var hash = data.userHash || data.anonHash || Hash.createRandomHash('drive'); - storeHash = hash; - if (!hash) { - throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...'); - } - // No password for drive - var secret = Hash.getSecrets('drive', hash); - var listmapConfig = { - data: {}, - websocketURL: NetConfig.getWebsocketURL(), - channel: secret.channel, - readOnly: false, - validateKey: secret.keys.validateKey || undefined, - crypto: Crypto.createEncryptor(secret.keys), - userName: 'fs', - logLevel: 1, - ChainPad: ChainPad, - classic: true, - }; - var rt = window.rt = Listmap.create(listmapConfig); - store.proxy = rt.proxy; - store.loggedIn = typeof(data.userHash) !== "undefined"; - - var returned = {}; - rt.proxy.on('create', function (info) { - store.realtime = info.realtime; - store.network = info.network; - if (!data.userHash) { - returned.anonHash = Hash.getEditHashFromKeys(secret); - } - }).on('ready', function () { - if (store.userObject) { return; } // the store is already ready, it is a reconnection - if (!rt.proxy.drive || typeof(rt.proxy.drive) !== 'object') { rt.proxy.drive = {}; } - var drive = rt.proxy.drive; - // Creating a new anon drive: import anon pads from localStorage - if ((!drive[Constants.oldStorageKey] || !Array.isArray(drive[Constants.oldStorageKey])) - && !drive['filesData']) { - drive[Constants.oldStorageKey] = []; - } - postMessage(clientId, 'LOADING_DRIVE', { state: 1 }); - // Drive already exist: return the existing drive, don't load data from legacy store - onReady(clientId, returned, cb); - }) - .on('change', ['drive', 'migrate'], function () { - var path = arguments[2]; - var value = arguments[1]; - if (path[0] === 'drive' && path[1] === "migrate" && value === 1) { - rt.network.disconnect(); - rt.realtime.abort(); - broadcast([], 'NETWORK_DISCONNECT'); + /** + * Data: + * - userHash or anonHash + * Todo in cb + * - LocalStore.setFSHash if needed + * - sessionStorage.User_Hash + * - stuff with tokenKey + * Event to outer + * - requestLogin + */ + var initialized = false; + Store.init = function (clientId, data, callback) { + if (initialized) { + return void callback({ + state: 'ALREADY_INIT', + returned: store.returned + }); } - }); - - rt.proxy.on('disconnect', function () { - broadcast([], 'NETWORK_DISCONNECT'); - }); - rt.proxy.on('reconnect', function (info) { - broadcast([], 'NETWORK_RECONNECT', {myId: info.myId}); - }); - }; + initialized = true; + 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(clientId, data, function (ret) { + if (Object.keys(store.proxy).length === 1) { + Feedback.send("FIRST_APP_USE", true); + } + store.returned = ret; - /** - * Data: - * - userHash or anonHash - * Todo in cb - * - LocalStore.setFSHash if needed - * - sessionStorage.User_Hash - * - stuff with tokenKey - * Event to outer - * - requestLogin - */ - var initialized = false; - Store.init = function (clientId, data, callback) { - if (initialized) { - return void callback({ - state: 'ALREADY_INIT', - returned: store.returned + callback(ret); }); - } - initialized = true; - 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(clientId, data, function (ret) { - if (Object.keys(store.proxy).length === 1) { - Feedback.send("FIRST_APP_USE", true); - } - store.returned = ret; - - callback(ret); - }); + Store.disconnect = function () { + if (!store.network) { return; } + store.network.disconnect(); + }; + return Store; }; - Store.disconnect = function () { - if (!store.network) { return; } - store.network.disconnect(); + return { + create: create }; - return Store; }); diff --git a/www/common/outer/serviceworker.js b/www/common/outer/serviceworker.js index 214df9abf..68afb4f32 100644 --- a/www/common/outer/serviceworker.js +++ b/www/common/outer/serviceworker.js @@ -27,10 +27,15 @@ var init = function (client, cb) { '/common/common-util.js', '/common/outer/worker-channel.js', '/common/outer/store-rpc.js' - ], function (Util, Channel, Rpc) { + ], function (Util, Channel, SRpc) { debug('SW Required ressources loaded'); var msgEv = Util.mkEvent(); + if (!self.Rpc) { + self.Rpc = SRpc(); + } + var Rpc = self.Rpc; + var postToClient = function (data) { postMsg(client, data); }; @@ -51,11 +56,17 @@ var init = function (client, cb) { console.error(e); console.log(data); } + if (q === "DISCONNECT") { + console.log('Deleting existing store!'); + delete self.Rpc; + delete self.store; + } }); }); chan.on('CONNECT', function (cfg, cb) { debug('SW Connect callback'); if (self.store) { + debug('Store already exists!'); if (cfg.driveEvents) { Rpc._subscribeToDrive(clientId); } diff --git a/www/common/outer/sharedworker.js b/www/common/outer/sharedworker.js index 14bfa1250..97686da24 100644 --- a/www/common/outer/sharedworker.js +++ b/www/common/outer/sharedworker.js @@ -27,10 +27,15 @@ var init = function (client, cb) { '/common/common-util.js', '/common/outer/worker-channel.js', '/common/outer/store-rpc.js' - ], function (Util, Channel, Rpc) { + ], function (Util, Channel, SRpc) { debug('SharedW Required ressources loaded'); var msgEv = Util.mkEvent(); + if (!self.Rpc) { + self.Rpc = SRpc(); + } + var Rpc = self.Rpc; + var postToClient = function (data) { postMsg(client, data); }; @@ -51,6 +56,11 @@ var init = function (client, cb) { console.error(e); console.log(data); } + if (q === "DISCONNECT") { + console.log('Deleting existing store!'); + delete self.Rpc; + delete self.store; + } }); }); chan.on('CONNECT', function (cfg, cb) { diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js index f44f8d1e2..f4fb180f1 100644 --- a/www/common/outer/store-rpc.js +++ b/www/common/outer/store-rpc.js @@ -1,89 +1,96 @@ define([ '/common/outer/async-store.js' -], function (Store) { - var Rpc = {}; +], function (AStore) { - var queries = Rpc.queries = { - // Ready - CONNECT: Store.init, - DISCONNECT: Store.disconnect, - CREATE_README: Store.createReadme, - MIGRATE_ANON_DRIVE: Store.migrateAnonDrive, - // RPC - INIT_RPC: Store.initRpc, - UPDATE_PIN_LIMIT: Store.updatePinLimit, - GET_PIN_LIMIT: Store.getPinLimit, - CLEAR_OWNED_CHANNEL: Store.clearOwnedChannel, - REMOVE_OWNED_CHANNEL: Store.removeOwnedChannel, - UPLOAD_CHUNK: Store.uploadChunk, - UPLOAD_COMPLETE: Store.uploadComplete, - UPLOAD_STATUS: Store.uploadStatus, - UPLOAD_CANCEL: Store.uploadCancel, - PIN_PADS: Store.pinPads, - UNPIN_PADS: Store.unpinPads, - GET_DELETED_PADS: Store.getDeletedPads, - GET_PINNED_USAGE: Store.getPinnedUsage, - // ANON RPC - INIT_ANON_RPC: Store.initAnonRpc, - ANON_RPC_MESSAGE: Store.anonRpcMsg, - GET_FILE_SIZE: Store.getFileSize, - GET_MULTIPLE_FILE_SIZE: Store.getMultipleFileSize, - // Store - GET: Store.get, - SET: Store.set, - ADD_PAD: Store.addPad, - SET_PAD_TITLE: Store.setPadTitle, - MOVE_TO_TRASH: Store.moveToTrash, - RESET_DRIVE: Store.resetDrive, - GET_METADATA: Store.getMetadata, - SET_DISPLAY_NAME: Store.setDisplayName, - SET_PAD_ATTRIBUTE: Store.setPadAttribute, - GET_PAD_ATTRIBUTE: Store.getPadAttribute, - SET_ATTRIBUTE: Store.setAttribute, - GET_ATTRIBUTE: Store.getAttribute, - LIST_ALL_TAGS: Store.listAllTags, - GET_TEMPLATES: Store.getTemplates, - GET_SECURE_FILES_LIST: Store.getSecureFilesList, - GET_PAD_DATA: Store.getPadData, - GET_STRONGER_HASH: Store.getStrongerHash, - INCREMENT_TEMPLATE_USE: Store.incrementTemplateUse, - // Messaging - INVITE_FROM_USERLIST: Store.inviteFromUserlist, - ADD_DIRECT_MESSAGE_HANDLERS: Store.addDirectMessageHandlers, - // Messenger - CONTACTS_GET_FRIEND_LIST: Store.messenger.getFriendList, - CONTACTS_GET_MY_INFO: Store.messenger.getMyInfo, - CONTACTS_GET_FRIEND_INFO: Store.messenger.getFriendInfo, - CONTACTS_REMOVE_FRIEND: Store.messenger.removeFriend, - CONTACTS_OPEN_FRIEND_CHANNEL: Store.messenger.openFriendChannel, - CONTACTS_GET_FRIEND_STATUS: Store.messenger.getFriendStatus, - CONTACTS_GET_MORE_HISTORY: Store.messenger.getMoreHistory, - CONTACTS_SEND_MESSAGE: Store.messenger.sendMessage, - CONTACTS_SET_CHANNEL_HEAD: Store.messenger.setChannelHead, - // Pad - SEND_PAD_MSG: Store.sendPadMsg, - JOIN_PAD: Store.joinPad, - GET_FULL_HISTORY: Store.getFullHistory, - IS_NEW_CHANNEL: Store.isNewChannel, - // Drive - DRIVE_USEROBJECT: Store.userObjectCommand, - // Settings, - DELETE_ACCOUNT: Store.deleteAccount, - }; + var create = function () { + var Store = AStore.create(); - Rpc.query = function (cmd, data, cb) { - if (queries[cmd]) { - queries[cmd]('0', data, cb); - } else { - console.error('UNHANDLED_STORE_RPC'); - } - }; + var Rpc = {}; + + var queries = Rpc.queries = { + // Ready + CONNECT: Store.init, + DISCONNECT: Store.disconnect, + CREATE_README: Store.createReadme, + MIGRATE_ANON_DRIVE: Store.migrateAnonDrive, + // RPC + INIT_RPC: Store.initRpc, + UPDATE_PIN_LIMIT: Store.updatePinLimit, + GET_PIN_LIMIT: Store.getPinLimit, + CLEAR_OWNED_CHANNEL: Store.clearOwnedChannel, + REMOVE_OWNED_CHANNEL: Store.removeOwnedChannel, + UPLOAD_CHUNK: Store.uploadChunk, + UPLOAD_COMPLETE: Store.uploadComplete, + UPLOAD_STATUS: Store.uploadStatus, + UPLOAD_CANCEL: Store.uploadCancel, + PIN_PADS: Store.pinPads, + UNPIN_PADS: Store.unpinPads, + GET_DELETED_PADS: Store.getDeletedPads, + GET_PINNED_USAGE: Store.getPinnedUsage, + // ANON RPC + INIT_ANON_RPC: Store.initAnonRpc, + ANON_RPC_MESSAGE: Store.anonRpcMsg, + GET_FILE_SIZE: Store.getFileSize, + GET_MULTIPLE_FILE_SIZE: Store.getMultipleFileSize, + // Store + GET: Store.get, + SET: Store.set, + ADD_PAD: Store.addPad, + SET_PAD_TITLE: Store.setPadTitle, + MOVE_TO_TRASH: Store.moveToTrash, + RESET_DRIVE: Store.resetDrive, + GET_METADATA: Store.getMetadata, + SET_DISPLAY_NAME: Store.setDisplayName, + SET_PAD_ATTRIBUTE: Store.setPadAttribute, + GET_PAD_ATTRIBUTE: Store.getPadAttribute, + SET_ATTRIBUTE: Store.setAttribute, + GET_ATTRIBUTE: Store.getAttribute, + LIST_ALL_TAGS: Store.listAllTags, + GET_TEMPLATES: Store.getTemplates, + GET_SECURE_FILES_LIST: Store.getSecureFilesList, + GET_PAD_DATA: Store.getPadData, + GET_STRONGER_HASH: Store.getStrongerHash, + INCREMENT_TEMPLATE_USE: Store.incrementTemplateUse, + // Messaging + INVITE_FROM_USERLIST: Store.inviteFromUserlist, + ADD_DIRECT_MESSAGE_HANDLERS: Store.addDirectMessageHandlers, + // Messenger + CONTACTS_GET_FRIEND_LIST: Store.messenger.getFriendList, + CONTACTS_GET_MY_INFO: Store.messenger.getMyInfo, + CONTACTS_GET_FRIEND_INFO: Store.messenger.getFriendInfo, + CONTACTS_REMOVE_FRIEND: Store.messenger.removeFriend, + CONTACTS_OPEN_FRIEND_CHANNEL: Store.messenger.openFriendChannel, + CONTACTS_GET_FRIEND_STATUS: Store.messenger.getFriendStatus, + CONTACTS_GET_MORE_HISTORY: Store.messenger.getMoreHistory, + CONTACTS_SEND_MESSAGE: Store.messenger.sendMessage, + CONTACTS_SET_CHANNEL_HEAD: Store.messenger.setChannelHead, + // Pad + SEND_PAD_MSG: Store.sendPadMsg, + JOIN_PAD: Store.joinPad, + GET_FULL_HISTORY: Store.getFullHistory, + IS_NEW_CHANNEL: Store.isNewChannel, + // Drive + DRIVE_USEROBJECT: Store.userObjectCommand, + // Settings, + DELETE_ACCOUNT: Store.deleteAccount, + }; - // Internal calls - Rpc._removeClient = Store._removeClient; - Rpc._subscribeToDrive = Store._subscribeToDrive; - Rpc._subscribeToMessenger = Store._subscribeToMessenger; + Rpc.query = function (cmd, data, cb) { + if (queries[cmd]) { + queries[cmd]('0', data, cb); + } else { + console.error('UNHANDLED_STORE_RPC'); + } + }; + + // Internal calls + Rpc._removeClient = Store._removeClient; + Rpc._subscribeToDrive = Store._subscribeToDrive; + Rpc._subscribeToMessenger = Store._subscribeToMessenger; + + return Rpc; + }; - return Rpc; + return create; }); diff --git a/www/common/outer/webworker.js b/www/common/outer/webworker.js index 4c3317364..201f3db9e 100644 --- a/www/common/outer/webworker.js +++ b/www/common/outer/webworker.js @@ -15,9 +15,11 @@ require([ '/common/common-util.js', '/common/outer/worker-channel.js', '/common/outer/store-rpc.js' - ], function (Util, Channel, Rpc) { + ], function (Util, Channel, SRpc) { var msgEv = Util.mkEvent(); + var Rpc = SRpc(); + Channel.create(msgEv, postMessage, function (chan) { var clientId = '1'; Object.keys(Rpc.queries).forEach(function (q) { From 1b490207531d03a547d5688d3ddd242697b21277 Mon Sep 17 00:00:00 2001 From: yflory Date: Tue, 12 Jun 2018 18:20:48 +0200 Subject: [PATCH 14/14] Add support for async store when WebWorker is not available --- www/common/cryptpad-common.js | 12 ++++++++---- www/common/outer/worker-channel.js | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 0a3724eba..08e1761c5 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -7,13 +7,12 @@ define([ '/common/common-constants.js', '/common/common-feedback.js', '/common/outer/local-store.js', - //'/common/outer/store-rpc.js', '/common/outer/worker-channel.js', '/customize/application_config.js', '/bower_components/nthen/index.js', ], function (Config, Messages, Util, Hash, - Messaging, Constants, Feedback, LocalStore, /*AStore, */Channel, + Messaging, Constants, Feedback, LocalStore, Channel, AppConfig, Nthen) { /* This file exposes functionality which is specific to Cryptpad, but not to @@ -867,8 +866,13 @@ define([ worker.postMessage(data); }; } else { - // TODO fallback no webworker? - console.error('NO SW OR WW'); + require(['/common/outer/noworker.js'], waitFor2(function (NoWorker) { + NoWorker.onMessage(function (data) { + msgEv.fire({data: data}); + }); + postMsg = function (d) { setTimeout(function () { NoWorker.query(d); }); }; + NoWorker.create(); + })); } }).nThen(function () { Channel.create(msgEv, postMsg, function (chan) { diff --git a/www/common/outer/worker-channel.js b/www/common/outer/worker-channel.js index a2129bd69..bff81a27b 100644 --- a/www/common/outer/worker-channel.js +++ b/www/common/outer/worker-channel.js @@ -9,7 +9,17 @@ define([ }; var create = function (onMsg, postMsg, cb, isWorker) { + if (!isWorker) { + var chanLoaded = false; + var waitingData = []; + onMsg.reg(function (data) { + if (chanLoaded) { return; } + waitingData.push(data); + }); + } + var evReady = Util.mkEvent(true); + var handlers = {}; var queries = {}; @@ -123,6 +133,12 @@ define([ }); if (isWorker) { evReady.fire(); + } else { + chanLoaded = true; + waitingData.forEach(function (d) { + onMsg.fire(d); + }); + waitingData = []; } cb(chan); };