From 47dee664daf7dda0caac8c16bd8f46d0787e9ec1 Mon Sep 17 00:00:00 2001 From: yflory Date: Fri, 8 Jun 2018 17:35:44 +0200 Subject: [PATCH] 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) {