diff --git a/www/auth/main.js b/www/auth/main.js index 5f1a5a333..a45751dcb 100644 --- a/www/auth/main.js +++ b/www/auth/main.js @@ -4,8 +4,9 @@ define([ '/common/common-constants.js', '/common/outer/local-store.js', '/common/test.js', + '/bower_components/nthen/index.js', '/bower_components/tweetnacl/nacl-fast.min.js' -], function ($, Cryptpad, Constants, LocalStore, Test) { +], function ($, Cryptpad, Constants, LocalStore, Test, nThen) { var Nacl = window.nacl; var signMsg = function (msg, privKey) { @@ -25,11 +26,18 @@ define([ localStorage[Constants.userHashKey] = localStorage[Constants.userHashKey] || sessionStorage[Constants.userHashKey]; - Cryptpad.ready(function () { + var proxy; + nThen(function (waitFor) { + Cryptpad.ready(waitFor()); + }).nThen(function (waitFor) { + Cryptpad.getUserObject(waitFor(function (obj) { + proxy = obj; + })); + }).nThen(function () { console.log('IFRAME READY'); Test(function () { // This is only here to maybe trigger an error. - window.drive = Cryptpad.getStore().getProxy().proxy['drive']; + window.drive = proxy['drive']; Test.passed(); }); $(window).on("message", function (jqe) { @@ -46,7 +54,6 @@ define([ } else if (!LocalStore.isLoggedIn()) { ret.error = "NOT_LOGGED_IN"; } else { - var proxy = Cryptpad.getStore().getProxy().proxy; var sig = signMsg(data.data, proxy.edPrivate); ret.res = { uname: proxy.login_name, diff --git a/www/common/common-constants.js b/www/common/common-constants.js index 76c70d103..044319069 100644 --- a/www/common/common-constants.js +++ b/www/common/common-constants.js @@ -10,5 +10,6 @@ define(function () { displayNameKey: 'cryptpad.username', oldStorageKey: 'CryptPad_RECENTPADS', storageKey: 'filesData', + tokenKey: 'loginToken', }; }); diff --git a/www/common/common-messenger.js b/www/common/common-messenger.js index 406b27482..09a527bfb 100644 --- a/www/common/common-messenger.js +++ b/www/common/common-messenger.js @@ -626,10 +626,11 @@ define([ }); }; + // TODO displayName messenger.getMyInfo = function (cb) { cb(void 0, { curvePublic: proxy.curvePublic, - displayName: common.getDisplayName(), + displayName: '' //common.getDisplayName(), }); }; diff --git a/www/common/common-realtime.js b/www/common/common-realtime.js index 6480d7e6b..87a92064b 100644 --- a/www/common/common-realtime.js +++ b/www/common/common-realtime.js @@ -1,17 +1,17 @@ define([ - '/customize/application_config.js', - '/customize/messages.js', - '/common/common-interface.js', -], function (AppConfig, Messages, UI) { + //'/customize/application_config.js', + //'/customize/messages.js', + //'/common/common-interface.js', +], function (/*AppConfig, Messages, UI*/) { var common = {}; - common.infiniteSpinnerDetected = false; - var BAD_STATE_TIMEOUT = typeof(AppConfig.badStateTimeout) === 'number'? - AppConfig.badStateTimeout: 30000; + //common.infiniteSpinnerDetected = false; + //var BAD_STATE_TIMEOUT = typeof(AppConfig.badStateTimeout) === 'number'? + // AppConfig.badStateTimeout: 30000; - var connected = false; - var intr; - var infiniteSpinnerHandlers = []; + //var connected = false; + //var intr; + //var infiniteSpinnerHandlers = []; /* TODO make this not blow up when disconnected or lagging... @@ -20,7 +20,7 @@ define([ if (typeof(realtime.getAuthDoc) !== 'function') { return void console.error('improper use of this function'); } - window.setTimeout(function () { + self.setTimeout(function () { if (realtime.getAuthDoc() === realtime.getUserDoc()) { return void cb(); } else { @@ -29,6 +29,7 @@ define([ }, 0); }; + /* common.beginDetectingInfiniteSpinner = function (realtime) { if (intr) { return; } intr = window.setInterval(function () { @@ -52,13 +53,14 @@ define([ common.infiniteSpinnerDetected = true; }, 2000); }; + */ - common.onInfiniteSpinner = function (f) { infiniteSpinnerHandlers.push(f); }; + //common.onInfiniteSpinner = function (f) { infiniteSpinnerHandlers.push(f); }; - common.setConnectionState = function (bool) { + /*common.setConnectionState = function (bool) { if (typeof(bool) !== 'boolean') { return; } connected = bool; - }; + };*/ return common; }); diff --git a/www/common/cryptget.js b/www/common/cryptget.js index 7a663480e..12bdd195e 100644 --- a/www/common/cryptget.js +++ b/www/common/cryptget.js @@ -73,12 +73,12 @@ define([ realtime.contentUpdate(doc); - var to = window.setTimeout(function () { + var to = self.setTimeout(function () { cb(new Error("Timeout")); }, 5000); Realtime.whenRealtimeSyncs(realtime, function () { - window.clearTimeout(to); + self.clearTimeout(to); realtime.abort(); finish(Session, void 0); }); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 181f6b7ae..d558f9e28 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -11,12 +11,13 @@ define([ '/common/common-constants.js', '/common/common-feedback.js', '/common/outer/local-store.js', + '/common/outer/store-rpc.js', '/common/pinpad.js', '/customize/application_config.js', '/bower_components/nthen/index.js', ], function ($, Config, Messages, Store, Util, Hash, - Messaging, Realtime, Language, Constants, Feedback, LocalStore, + Messaging, Realtime, Language, Constants, Feedback, LocalStore, AStore, Pinpad, AppConfig, Nthen) { /* This file exposes functionality which is specific to Cryptpad, but not to @@ -25,6 +26,18 @@ define([ Additionally, there is some basic functionality for import/export. */ + var postMessage = function (cmd, data, cb) { + setTimeout(function () { + AStore.query(cmd, data, cb); + }); + }; + var tryParsing = function (x) { + try { return JSON.parse(x); } + catch (e) { + console.error(e); + return null; + } + }; var origin = encodeURIComponent(window.location.hostname); var common = window.Cryptpad = { @@ -40,20 +53,11 @@ define([ var rpc; var anon_rpc; - var getStore = common.getStore = function () { - if (store) { return store; } - throw new Error("Store is not ready!"); - }; var getProxy = common.getProxy = function () { if (store && store.getProxy()) { return store.getProxy().proxy; } }; - common.getFO = function () { - if (store && store.getProxy()) { - return store.getProxy().fo; - } - }; var getNetwork = common.getNetwork = function () { if (store) { if (store.getProxy() && store.getProxy().info) { @@ -63,7 +67,59 @@ define([ return; }; - // REFACTOR pull language directly + // RESTRICTED + // Settings only + common.getUserObject = function (cb) { + postMessage("GET", [], function (obj) { + cb(obj); + }); + }; + common.resetDrive = function (cb) { + postMessage("RESET_DRIVE", null, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(); + }); + }; + common.logoutFromAll = function (cb) { + var token = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); + localStorage.setItem(Constants.tokenKey, token); + postMessage("SET", { + key: [Constants.tokenKey], + value: token + }, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(); + }); + }; + // Settings and auth + common.getUserObject = function (cb) { + postMessage("GET", [], function (obj) { + cb(obj); + }); + }; + // Settings and ready + common.mergeAnonDrive = function (cb) { + var data = { + anonHash: LocalStore.getFSHash() + }; + postMessage("MIGRATE_ANON_DRIVE", data, cb); + }; + // Profile + common.getProfileEditUrl = function (cb) { + postMessage("GET", ['profile', 'edit'], function (obj) { + cb(obj); + }); + }; + common.setNewProfile = function (profile) { + postMessage("SET", { + key: ['profile'], + value: profile + }, function () {}); + }; + + + + // REFACTOR pull language directly? common.getLanguage = function () { return Messages._languageUsed; }; @@ -72,30 +128,11 @@ define([ }; // REAFCTOR store.getProfile should be store.get(['profile']) - common.getProfileUrl = function () { - if (store && store.getProfile()) { - return store.getProfile().view; - } - }; - common.getAvatarUrl = function () { - if (store && store.getProfile()) { - return store.getProfile().avatar; - } - }; - common.getDisplayName = function (cb) { - var name; - if (getProxy()) { - name = getProxy()[Constants.displayNameKey]; - } - name = name || ''; - if (typeof cb === "function") { cb(null, name); } - return name; - }; - - common.getUid = function () { - if (store && store.getProxy() && store.getProxy().proxy) { - return store.getProxy().proxy.uid; - } + common.getMetadata = function (cb) { + postMessage("GET_METADATA", null, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(null, obj); + }); }; var getRealtime = common.getRealtime = function () { @@ -105,27 +142,20 @@ define([ return; }; + // TODO not needed with async store common.hasSigningKeys = function (proxy) { return typeof(proxy) === 'object' && typeof(proxy.edPrivate) === 'string' && typeof(proxy.edPublic) === 'string'; }; + // TODO not needed with async store common.hasCurveKeys = function (proxy) { return typeof(proxy) === 'object' && typeof(proxy.curvePrivate) === 'string' && typeof(proxy.curvePublic) === 'string'; }; - common.getPublicKeys = function (proxy) { - proxy = proxy || common.getProxy(); - if (!proxy || !proxy.edPublic || !proxy.curvePublic) { return; } - return { - curve: proxy.curvePublic, - ed: proxy.edPublic, - }; - }; - var makePad = common.makePad = function (href, title) { var now = +new Date(); return { @@ -136,152 +166,145 @@ define([ }; }; + common.setDisplayName = function (value, cb) { + postMessage("SET_DISPLAY_NAME", value, cb); + }; + // STORAGE common.setPadAttribute = function (attr, value, cb, href) { href = Hash.getRelativeHref(href || window.location.href); - getStore().setPadAttribute(href, attr, value, cb); - }; - common.setDisplayName = function (value, cb) { - if (getProxy()) { - getProxy()[Constants.displayNameKey] = value; - } - if (typeof cb === "function") { Realtime.whenRealtimeSyncs(getRealtime(), cb); } - }; - common.setAttribute = function (attr, value, cb) { - getStore().setAttribute(attr, value, function (err, data) { - if (cb) { cb(err, data); } + postMessage("SET_PAD_ATTRIBUTE", { + href: href, + attr: attr, + value: value + }, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(); }); }; - - // STORAGE common.getPadAttribute = function (attr, cb) { var href = Hash.getRelativeHref(window.location.href); - getStore().getPadAttribute(href, attr, cb); + postMessage("GET_PAD_ATTRIBUTE", { + href: href, + attr: attr, + }, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj); + }); + }; + common.setAttribute = function (attr, value, cb) { + postMessage("SET_ATTRIBUTE", { + attr: attr, + value: value + }, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(); + }); }; common.getAttribute = function (attr, cb) { - getStore().getAttribute(attr, function (err, data) { - cb(err, data); + postMessage("GET_ATTRIBUTE", { + attr: attr + }, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(null, obj); }); }; - /* this returns a reference to your proxy. changing it will change your drive. - */ - var getFileEntry = common.getFileEntry = function (href, cb) { - if (typeof(cb) !== 'function') { return; } - var store = getStore(); - if (!store) { return void cb('NO_STORE'); } - href = href || (window.location.pathname + window.location.hash); - var id = store.getIdFromHref(href); - if (!id) { return void cb('NO_ID'); } - var entry = Util.find(getProxy(), [ - 'drive', - 'filesData', - id - ]); - cb(void 0, entry); - }; - + // Tags common.resetTags = function (href, tags, cb) { + // set pad attribute cb = cb || $.noop; if (!Array.isArray(tags)) { return void cb('INVALID_TAGS'); } - getFileEntry(href, function (e, entry) { - if (e) { return void cb(e); } - if (!entry) { cb('NO_ENTRY'); } - entry.tags = tags.slice(); - cb(); - }); + common.setPadAttribute('tags', tags.slice(), cb, href); }; - common.tagPad = function (href, tag, cb) { if (typeof(cb) !== 'function') { return void console.error('EXPECTED_CALLBACK'); } if (typeof(tag) !== 'string') { return void cb('INVALID_TAG'); } - getFileEntry(href, function (e, entry) { + common.getPadAttribute('tags', function (e, tags) { if (e) { return void cb(e); } - if (!entry) { cb('NO_ENTRY'); } - if (!entry.tags) { - entry.tags = [tag]; - } else if (entry.tags.indexOf(tag) === -1) { - entry.tags.push(tag); + var newTags; + if (!tags) { + newTags = [tag]; + } else if (tags.indexOf(tag) === -1) { + newTags = tags.slice(); + newTags.push(tag); } - cb(); - }); + common.setPadAttribute('tags', newTags, cb, href); + }, href); }; - common.untagPad = function (href, tag, cb) { if (typeof(cb) !== 'function') { return void console.error('EXPECTED_CALLBACK'); } if (typeof(tag) !== 'string') { return void cb('INVALID_TAG'); } - getFileEntry(href, function (e, entry) { + common.getPadAttribute('tags', function (e, tags) { if (e) { return void cb(e); } - if (!entry) { cb('NO_ENTRY'); } - if (!entry.tags) { return void cb(); } - var idx = entry.tags.indexOf(tag); + if (!tags) { return void cb(); } + var idx = tags.indexOf(tag); if (idx === -1) { return void cb(); } - entry.tags.splice(idx, 1); - cb(); - }); + var newTags = tags.slice(); + newTags.splice(idx, 1); + common.setPadAttribute('tags', newTags, cb, href); + }, href); }; - common.getPadTags = function (href, cb) { if (typeof(cb) !== 'function') { return; } - getFileEntry(href, function (e, entry) { - if (entry) { - return void cb(void 0, entry.tags? - JSON.parse(JSON.stringify(entry.tags)): []); - } - return cb('NO_ENTRY'); - }); + common.getPadAttribute('tags', function (e, tags) { + if (e) { return void cb(e); } + cb(void 0, tags ? tags.slice() : []); + }, href); }; - common.listAllTags = function (cb) { - var all = []; - var proxy = getProxy(); - var files = Util.find(proxy, ['drive', 'filesData']); - - if (typeof(files) !== 'object') { return cb('invalid_drive'); } - Object.keys(files).forEach(function (k) { - var file = files[k]; - if (!Array.isArray(file.tags)) { return; } - file.tags.forEach(function (tag) { - if (all.indexOf(tag) === -1) { - all.push(tag); - } - }); + postMessage("LIST_ALL_TAGS", null, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(void 0, obj); }); - cb(void 0, all); }; // STORAGE - TEMPLATES - var listTemplates = common.listTemplates = function (type) { - var allTemplates = getStore().listTemplates(); - if (!type) { return allTemplates; } - - var templates = allTemplates.filter(function (f) { - var parsed = Hash.parsePadUrl(f.href); - return parsed.type === type; + common.listTemplates = function (type, cb) { + postMessage("GET_TEMPLATES", null, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + if (!Array.isArray(obj)) { return void cb ('NOT_AN_ARRAY'); } + if (!type) { return void cb(obj); } + + var templates = obj.filter(function (f) { + var parsed = Hash.parsePadUrl(f.href); + return parsed.type === type; + }); + cb(templates); }); - return templates; }; - common.addTemplate = function (data) { - getStore().pushData(data, function (e, id) { - if (e) { return void console.error("Error while adding a template:", e); } // TODO LIMIT - getStore().addPad(id, ['template']); + + common.saveAsTemplate = function (Cryptput, data, cb) { + var p = Hash.parsePadUrl(window.location.href); + if (!p.type) { return; } + var hash = Hash.createRandomHash(); + var href = '/' + p.type + '/#' + hash; + Cryptput(hash, data.toSave, function (e) { + if (e) { throw new Error(e); } + postMessage("ADD_PAD", { + href: href, + title: data.title + }, function (obj) { + if (obj && obj.error) { return void cb(obj.error); } + cb(); + }); }); }; - common.isTemplate = function (href) { + common.isTemplate = function (href, cb) { var rhref = Hash.getRelativeHref(href); - var templates = listTemplates(); - return templates.some(function (t) { - return t.href === rhref; + common.listTemplates(null, function (templates) { + cb(void 0, templates.some(function (t) { + return t.href === rhref; + })); }); }; - // Secure iframes common.useTemplate = function (href, Crypt, cb) { var parsed = Hash.parsePadUrl(href); if(!parsed) { throw new Error("Cannot get template hash"); } @@ -292,363 +315,115 @@ define([ }); }; - // STORAGE - /* fetch and migrate your pad history from the store */ - var getRecentPads = common.getRecentPads = function (cb) { - getStore().getDrive('filesData', function (err, recentPads) { - if (typeof(recentPads) === "object") { - cb(void 0, recentPads); - return; - } - cb(void 0, {}); - }); - }; - - // STORAGE: Display Name - common.getLastName = common.getDisplayName; - /* function (cb) { - common.getDisplayName(function (err, userName) { - cb(err, userName); - }); - };*/ - var _onDisplayNameChanged = []; - common.onDisplayNameChanged = function (h) { - if (typeof(h) !== "function") { return; } - if (_onDisplayNameChanged.indexOf(h) !== -1) { return; } - _onDisplayNameChanged.push(h); - }; - common.changeDisplayName = function (newName, isLocal) { - _onDisplayNameChanged.forEach(function (h) { - h(newName, isLocal); - }); - }; - - // STORAGE - common.forgetPad = function (href, cb) { - if (typeof(getStore().forgetPad) === "function") { - getStore().forgetPad(Hash.getRelativeHref(href), cb); - return; - } - cb ("store.forgetPad is not a function"); + // Forget button + common.moveToTrash = function (cb, href) { + href = href || window.location.href; + postMessage("MOVE_TO_TRASH", { href: href }, cb); }; - common.setPadTitle = function (name, padHref, cb) { - var href = typeof padHref === "string" ? padHref : window.location.href; + // When opening a new pad or renaming it, store the new title + common.setPadTitle = function (title, padHref, cb) { + var href = padHref || window.location.href; var parsed = Hash.parsePadUrl(href); if (!parsed.hash) { return; } href = parsed.getUrl({present: parsed.present}); - //href = Hash.getRelativeHref(href); - // getRecentPads return the array from the drive, not a copy - // We don't have to call "set..." at the end, everything is stored with listmap - getRecentPads(function (err, recent) { - if (err) { - cb(err); - return; - } - - var updateWeaker = []; - var contains; - Object.keys(recent).forEach(function (id) { - var pad = recent[id]; - var p = Hash.parsePadUrl(pad.href); - - if (p.type !== parsed.type) { return pad; } - - var shouldUpdate = p.hash.replace(/\/$/, '') === parsed.hash.replace(/\/$/, ''); - - // Version 1 : we have up to 4 differents hash for 1 pad, keep the strongest : - // Edit > Edit (present) > View > View (present) - var pHash = p.hashData; - var parsedHash = parsed.hashData; - - if (!pHash) { return; } // We may have a corrupted pad in our storage, abort here in that case - - if (!shouldUpdate && pHash.version === 1 && parsedHash.version === 1 && pHash.channel === parsedHash.channel) { - if (pHash.mode === 'view' && parsedHash.mode === 'edit') { shouldUpdate = true; } - else if (pHash.mode === parsedHash.mode && pHash.present) { shouldUpdate = true; } - else { - // Editing a "weaker" version of a stored hash : update the date and do not push the current hash - pad.atime = +new Date(); - contains = true; - return pad; - } - } - - if (shouldUpdate) { - contains = true; - // update the atime - pad.atime = +new Date(); - - // set the name - pad.title = name; - - // If we now have a stronger version of a stored href, replace the weaker one by the strong one - if (pad && pad.href && href !== pad.href) { - updateWeaker.push({ - o: pad.href, - n: href - }); - } - pad.href = href; - } - return pad; - }); - - if (updateWeaker.length > 0) { - updateWeaker.forEach(function (obj) { - // If we have a stronger url, and if all the occurences of the weaker were - // in the trash, add remove them from the trash and add the stronger in root - getStore().restoreHref(obj.n); - }); - } - if (!contains && href) { - var data = makePad(href, name); - getStore().pushData(data, function (e, id) { - if (e) { - return void cb(e); - } - getStore().addPad(id, common.initialPath); - cb(err, recent); - }); - return; - } - cb(err, recent); - }); - }; - /* - * Buttons - */ - common.renamePad = function (title, href, callback) { if (title === null) { return; } + if (title.trim() === "") { title = Hash.getDefaultName(parsed); } - if (title.trim() === "") { - var parsed = Hash.parsePadUrl(href || window.location.href); - title = Hash.getDefaultName(parsed); - } - - common.setPadTitle(title, href, function (err) { - if (err) { + postMessage("SET_PAD_TITLE", { + href: href, + title: title + }, function (obj) { + if (obj && obj.error) { console.log("unable to set pad title"); - console.error(err); - return; + return void cb(obj.error); } - callback(null, title); + cb(); }); }; // Needed for the secure filepicker app common.getSecureFilesList = function (query, cb) { - var store = common.getStore(); - if (!store) { return void cb("Store is not ready"); } - var proxy = store.getProxy(); - var fo = proxy.fo; - 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; - }; - fo.getFiles(where).forEach(function (id) { - var data = fo.getFileData(id); - var parsed = Hash.parsePadUrl(data.href); - if ((!types || types.length === 0 || types.indexOf(parsed.type) !== -1) - && hashes.indexOf(parsed.hash) === -1) { - if (isFiltered(parsed.type, data)) { return; } - hashes.push(parsed.hash); - list[id] = data; - } + postMessage("GET_SECURE_FILES_LIST", query, function (list) { + cb(void 0, list); }); - cb (null, list); - }; - - var getUserChannelList = common.getUserChannelList = function () { - var store = common.getStore(); - var proxy = store.getProxy(); - var fo = proxy.fo; - - // start with your userHash... - var userHash = localStorage && localStorage[Constants.userHashKey]; - if (!userHash) { return null; } - - var userParsedHash = Hash.parseTypeHash('drive', userHash); - var userChannel = userParsedHash && userParsedHash.channel; - if (!userChannel) { return null; } - - var list = fo.getFiles([fo.FILES_DATA]).map(function (id) { - return Hash.hrefToHexChannelId(fo.getFileData(id).href); - }) - .filter(function (x) { return x; }); - - // Get the avatar - var profile = store.getProfile(); - if (profile) { - var profileChan = profile.edit ? Hash.hrefToHexChannelId('/profile/#' + profile.edit) : null; - if (profileChan) { list.push(profileChan); } - var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar) : null; - if (avatarChan) { list.push(avatarChan); } - } - - if (getProxy().friends) { - var fList = Messaging.getFriendChannelsList(common); - list = list.concat(fList); - } - - list.push(Util.base64ToHex(userChannel)); - list.sort(); - - return list; - }; - - var getCanonicalChannelList = common.getCanonicalChannelList = function () { - return Util.deduplicateString(getUserChannelList()).sort(); - }; - - var pinsReady = common.pinsReady = function () { - if (!LocalStore.isLoggedIn()) { - return false; - } - if (!PINNING_ENABLED) { - console.error('[PINNING_DISABLED]'); - return false; - } - if (!rpc) { - console.error('RPC_NOT_READY'); - return false; - } - return true; }; common.arePinsSynced = function (cb) { - if (!pinsReady()) { return void cb ('RPC_NOT_READY'); } - - var list = getCanonicalChannelList(); - var local = Hash.hashChannelList(list); - rpc.getServerHash(function (e, hash) { - if (e) { return void cb(e); } - cb(void 0, hash === local); + postMessage("ARE_PINS_SYNCED", null, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(); }); }; common.resetPins = function (cb) { - if (!pinsReady()) { return void cb ('RPC_NOT_READY'); } - - var list = getCanonicalChannelList(); - rpc.reset(list, function (e, hash) { - if (e) { return void cb(e); } - cb(void 0, hash); + postMessage("RESET_PINS", null, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(); }); }; common.pinPads = function (pads, cb) { - if (!pinsReady()) { return void cb ('RPC_NOT_READY'); } - if (typeof(cb) !== 'function') { - console.error('expected a callback'); - } - - rpc.pin(pads, function (e, hash) { - if (e) { return void cb(e); } - cb(void 0, hash); + postMessage("PIN_PADS", {pads: pads}, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(); }); }; common.unpinPads = function (pads, cb) { - if (!pinsReady()) { return void cb ('RPC_NOT_READY'); } - - rpc.unpin(pads, function (e, hash) { - if (e) { return void cb(e); } - cb(void 0, hash); + postMessage("UNPIN_PADS", {pads: pads}, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(); }); }; common.getPinnedUsage = function (cb) { - if (!pinsReady()) { return void cb('RPC_NOT_READY'); } - - rpc.getFileListSize(function (err, bytes) { - if (typeof(bytes) === 'number') { - common.account.usage = bytes; - } - cb(err, bytes); + postMessage("GET_PINNED_USAGE", null, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(); }); }; // SFRAME: talk to anon_rpc from the iframe common.anonRpcMsg = function (msg, data, cb) { if (!msg) { return; } - if (!anon_rpc) { return void cb('ANON_RPC_NOT_READY'); } - anon_rpc.send(msg, data, cb); + postMessage("ANON_RPC_MESSAGE", { + msg: msg, + data: data + }, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(); + }); }; common.getFileSize = function (href, cb) { - if (!anon_rpc) { return void cb('ANON_RPC_NOT_READY'); } - //if (!pinsReady()) { return void cb('RPC_NOT_READY'); } - var channelId = Hash.hrefToHexChannelId(href); - anon_rpc.send("GET_FILE_SIZE", channelId, function (e, response) { - if (e) { return void cb(e); } - if (response && response.length && typeof(response[0]) === 'number') { - return void cb(void 0, response[0]); - } else { - cb('INVALID_RESPONSE'); - } + postMessage("GET_FILE_SIZE", {href: href}, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(undefined, obj.size); }); }; + // TODO not used anymore? common.getMultipleFileSize = function (files, cb) { - if (!anon_rpc) { return void cb('ANON_RPC_NOT_READY'); } - if (!Array.isArray(files)) { - return void setTimeout(function () { cb('INVALID_FILE_LIST'); }); - } - - anon_rpc.send('GET_MULTIPLE_FILE_SIZE', files, function (e, res) { - if (e) { return cb(e); } - if (res && res.length && typeof(res[0]) === 'object') { - cb(void 0, res[0]); - } else { - cb('UNEXPECTED_RESPONSE'); - } + postMessage("GET_MULTIPLE_FILE_SIZE", {files:files}, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(undefined, obj.size); }); }; common.updatePinLimit = function (cb) { - if (!pinsReady()) { return void cb('RPC_NOT_READY'); } - rpc.updatePinLimits(function (e, limit, plan, note) { - if (e) { return cb(e); } - common.account.limit = limit; - common.account.plan = plan; - common.account.note = note; - cb(e, limit, plan, note); + postMessage("UPDATE_PIN_LIMIT", null, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(undefined, obj.limit, obj.plan, obj.note); }); }; common.getPinLimit = function (cb) { - if (!pinsReady()) { return void cb('RPC_NOT_READY'); } - - var account = common.account; - - var ALWAYS_REVALIDATE = true; - if (ALWAYS_REVALIDATE || typeof(account.limit) !== 'number' || - typeof(account.plan) !== 'string' || - typeof(account.note) !== 'string') { - return void rpc.getLimit(function (e, limit, plan, note) { - if (e) { return cb(e); } - common.account.limit = limit; - common.account.plan = plan; - common.account.note = note; - cb(void 0, limit, plan, note); - }); - } - - cb(void 0, account.limit, account.plan, account.note); + postMessage("GET_PIN_LIMIT", null, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(undefined, obj.limit, obj.plan, obj.note); + }); }; common.isOverPinLimit = function (cb) { @@ -671,87 +446,55 @@ define([ }; common.clearOwnedChannel = function (channel, cb) { - if (!pinsReady()) { return void cb('RPC_NOT_READY'); } - rpc.clearOwnedChannel(channel, cb); + postMessage("CLEAR_OWNED_CHANNEL", {channel: channel}, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(); + }); }; common.uploadComplete = function (cb) { - if (!pinsReady()) { return void cb('RPC_NOT_READY'); } - rpc.uploadComplete(cb); + postMessage("UPLOAD_COMPLETE", null, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(); + }); }; common.uploadStatus = function (size, cb) { - if (!pinsReady()) { return void cb('RPC_NOT_READY'); } - rpc.uploadStatus(size, cb); + postMessage("UPLOAD_STATUS", {size: size}, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(); + }); }; common.uploadCancel = function (cb) { - if (!pinsReady()) { return void cb('RPC_NOT_READY'); } - rpc.uploadCancel(cb); - }; - - // Forget button - // TODO REFACTOR only used in sframe-common-outer - common.moveToTrash = function (cb, href) { - href = href || window.location.href; - common.forgetPad(href, function (err) { - if (err) { - console.log("unable to forget pad"); - console.error(err); - cb(err, null); - return; - } - var n = getNetwork(); - var r = getRealtime(); - if (n && r) { - Realtime.whenRealtimeSyncs(r, function () { - n.disconnect(); - cb(); - }); - } else { - cb(); - } + postMessage("UPLOAD_CANCEL", null, function (obj) { + if (obj.error) { return void cb(obj.error); } + cb(); }); }; - // TODO REFACTOR only used in sframe-common-outer - common.saveAsTemplate = function (Cryptput, data, cb) { - var p = Hash.parsePadUrl(window.location.href); - if (!p.type) { return; } - var hash = Hash.createRandomHash(); - var href = '/' + p.type + '/#' + hash; - Cryptput(hash, data.toSave, function (e) { - if (e) { throw new Error(e); } - common.addTemplate(makePad(href, data.title)); - Realtime.whenRealtimeSyncs(getRealtime(), function () { - cb(); - }); - }); - }; + // HERE common.getShareHashes = function (secret, cb) { if (!window.location.hash) { var hashes = Hash.getHashes(secret.channel, secret); return void cb(null, hashes); } - common.getRecentPads(function (err, recent) { - var parsed = Hash.parsePadUrl(window.location.href); - if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); } - if (parsed.type === 'file') { secret.channel = Util.base64ToHex(secret.channel); } - var hashes = Hash.getHashes(secret.channel, secret); - - if (!hashes.editHash && !hashes.viewHash && parsed.hashData && !parsed.hashData.mode) { - // It means we're using an old hash - hashes.editHash = window.location.hash.slice(1); - } - - // If we have a stronger version in drive, add it and add a redirect button - var stronger = recent && Hash.findStronger(null, recent); - if (stronger) { - var parsed2 = Hash.parsePadUrl(stronger); - hashes.editHash = parsed2.hash; - } + var parsed = Hash.parsePadUrl(window.location.href); + if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); } + if (parsed.type === 'file') { secret.channel = Util.base64ToHex(secret.channel); } + var hashes = Hash.getHashes(secret.channel, secret); + + if (!hashes.editHash && !hashes.viewHash && parsed.hashData && !parsed.hashData.mode) { + // It means we're using an old hash + hashes.editHash = window.location.hash.slice(1); + return void cb(null, hashes); + } + postMessage("GET_STRONGER_HASH", { + href: href + }, function (hash) { + if (hash) { hashes.editHash = hash; } cb(null, hashes); }); }; @@ -777,6 +520,44 @@ define([ localStorage[CRYPTPAD_VERSION] = ver; }; + var _onMetadataChanged = []; + common.onMetadataChanged = function (h) { + if (typeof(h) !== "function") { return; } + if (_onMetadataChanged.indexOf(h) !== -1) { return; } + _onMetadataChanged.push(h); + }; + common.changeMetadata = function () { + _onMetadataChanged.forEach(function (h) { h(); }); + }; + + var requestLogin = function () { + // log out so that you don't go into an endless loop... + LocalStore.logout(); + + // redirect them to log in, and come back when they're done. + sessionStorage.redirectTo = window.location.href; + window.location.href = '/login/'; + }; + + var onMessage = function (cmd, data, cb) { + cb = cb || function () {}; + 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; + } + } + }; + common.ready = (function () { var env = {}; var initialized = false; @@ -786,18 +567,13 @@ define([ return void setTimeout(function () { f(void 0, env); }); } + // TODO if (sessionStorage[Constants.newPadPathKey]) { common.initialPath = sessionStorage[Constants.newPadPathKey]; delete sessionStorage[Constants.newPadPathKey]; } - var proxy; - var network; var provideFeedback = function () { - if (Object.keys(proxy).length === 1) { - Feedback.send("FIRST_APP_USE", true); - } - if (typeof(window.Proxy) === 'undefined') { Feedback.send("NO_PROXIES"); } @@ -817,36 +593,42 @@ define([ Feedback.reportScreenDimensions(); Feedback.reportLanguage(); }; - var initFeedback = function () { + var initFeedback = function (feedback) { // Initialize feedback - try { - var entry = Util.find(getProxy(), [ - 'settings', - 'general', - 'allowUserFeedback' - ]); - Feedback.init(entry); - } catch (e) { - console.error(e); - Feedback.init(false); - } + Feedback.init(feedback); provideFeedback(); }; Nthen(function (waitFor) { - Store.ready(waitFor(function (err, storeObj) { - store = common.store = env.store = storeObj; - Messaging.addDirectMessageHandler(common); - proxy = getProxy(); - network = getNetwork(); - network.on('disconnect', function () { - Realtime.setConnectionState(false); - }); - network.on('reconnect', function () { - Realtime.setConnectionState(true); - }); - initFeedback(); - }), common); + var cfg = { + query: onMessage, // TODO temporary, will be replaced by a webworker channel + userHash: LocalStore.getUserHash(), + anonHash: LocalStore.getFSHash(), + localToken: localStorage.getItem(Constants.tokenKey) + }; + AStore.query("CONNECT", cfg, waitFor(function (data) { + if (data.error) { throw new Error(data.error); } + + 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]); + } + } + + // TODO ww + //Messaging.addDirectMessageHandler(common); + initFeedback(data.feedback); + })); }).nThen(function (waitFor) { $(waitFor()); }).nThen(function (waitFor) { @@ -876,114 +658,40 @@ define([ document.location.reload(); } else if (o && !n) { LocalStore.logout(); - if (getNetwork()) { - getNetwork().disconnect(); - } + postMessage("DISCONNECT"); } }); if (PINNING_ENABLED && LocalStore.isLoggedIn()) { console.log("logged in. pads will be pinned"); - var w0 = waitFor(); - Pinpad.create(network, proxy, function (e, call) { - if (e) { - console.error(e); - return w0(); - } - + postMessage("INIT_RPC", null, waitFor(function () { console.log('RPC handshake complete'); - rpc = common.rpc = env.rpc = call; - - common.getPinLimit(function (e, limit, plan, note) { - if (e) { return void console.error(e); } - common.account.limit = limit; - localStorage.plan = common.account.plan = plan; - common.account.note = note; - w0(); - }); - - common.arePinsSynced(function (err, yes) { - if (!yes) { - common.resetPins(function (err) { - if (err) { - console.error("Pin Reset Error"); - return console.error(err); - } - console.log('RESET DONE'); - }); - } - }); - }); + })); } else if (PINNING_ENABLED) { console.log('not logged in. pads will not be pinned'); } else { console.log('pinning disabled'); } - var w1 = waitFor(); - require([ - '/common/rpc.js', - ], function (Rpc) { - Rpc.createAnonymous(network, function (e, call) { - if (e) { - console.error(e); - return void w1(); - } - anon_rpc = common.anon_rpc = env.anon_rpc = call; - w1(); - }); - }); - - // Everything's ready, continue... - if($('#pad-iframe').length) { - var w2 = waitFor(); - var $iframe = $('#pad-iframe'); - var iframe = $iframe[0]; - var iframeDoc = iframe.contentDocument || iframe.contentWindow.document; - if (iframeDoc.readyState === 'complete') { - return void w2(); - } - $iframe.load(w2); //cb); - } + postMessage("INIT_ANON_RPC", null, waitFor(function () { + console.log('Anonymous RPC ready'); + })); }).nThen(function (waitFor) { if (sessionStorage.createReadme) { - var w = waitFor(); - require(['/common/cryptget.js'], function (Crypt) { - var hash = Hash.createRandomHash(); - Crypt.put(hash, Messages.driveReadme, function (e) { - if (e) { - console.error("Error while creating the default pad:", e); - return void w(); - } - var href = '/pad/#' + hash; - var data = { - href: href, - title: Messages.driveReadmeTitle, - atime: +new Date(), - ctime: +new Date() - }; - common.getFO().pushData(data, function (e, id) { - if (e) { - console.error("Error while creating the default pad:", e); - return void w(); - } - common.getFO().add(id); - w(); - }); - }); + var data = { + driveReadme: Messages.driveReadme, + driveReadmeTitle: Messages.driveReadmeTitle, + }; + postMessage("CREATE_README", data, waitFor(function (e) { + if (e && e.error) { return void console.error(e.error); } delete sessionStorage.createReadme; - }); + })); } }).nThen(function (waitFor) { if (sessionStorage.migrateAnonDrive) { - var w = waitFor(); - require(['/common/mergeDrive.js'], function (Merge) { - var hash = LocalStore.getFSHash(); - Merge.anonDriveIntoUser(getStore().getProxy(), hash, function () { - delete sessionStorage.migrateAnonDrive; - w(); - }); - }); + common.mergeAnonDrive(waitFor(function() { + delete sessionStorage.migrateAnonDrive; + })); } }).nThen(function () { updateLocalVersion(); diff --git a/www/common/fsStore.js b/www/common/fsStore.js index 6ef2f6d51..3c502a0ee 100644 --- a/www/common/fsStore.js +++ b/www/common/fsStore.js @@ -194,8 +194,8 @@ define([ }; var onReady = function (f, proxy, Cryptpad, exp) { - var fo = exp.fo = FO.init(proxy.drive, { - Cryptpad: Cryptpad, + var fo = exp.userObject = exp.fo = FO.init(proxy.drive, { + pinPads: Cryptpad.pinPads, loggedIn: LocalStore.isLoggedIn() }); var todo = function () { @@ -265,15 +265,15 @@ define([ proxy.on('change', [Constants.displayNameKey], function (o, n) { if (typeof(n) !== "string") { return; } - Cryptpad.changeDisplayName(n); + Cryptpad.changeMetadata(); }); proxy.on('change', ['profile'], function () { // Trigger userlist update when the avatar has changed - Cryptpad.changeDisplayName(proxy[Constants.displayNameKey]); + Cryptpad.changeMetadata(); }); proxy.on('change', ['friends'], function () { - // Trigger userlist update when the avatar has changed - Cryptpad.changeDisplayName(proxy[Constants.displayNameKey]); + // Trigger userlist update when the friendlist has changed + Cryptpad.changeMetadata(); }); proxy.on('change', [tokenKey], function () { var localToken = tryParsing(localStorage.getItem(tokenKey)); @@ -315,6 +315,7 @@ define([ exp.realtime = rt.realtime; exp.proxy = rt.proxy; + exp.loggedIn = Cryptpad.isLoggedIn() rt.proxy.on('create', function (info) { exp.info = info; if (!LocalStore.getUserHash()) { diff --git a/www/common/mergeDrive.js b/www/common/mergeDrive.js index d61a1db18..ba831ab56 100644 --- a/www/common/mergeDrive.js +++ b/www/common/mergeDrive.js @@ -2,8 +2,8 @@ define([ '/common/cryptget.js', '/common/userObject.js', '/common/common-hash.js', - '/common/outer/local-store.js', -], function (Crypt, FO, Hash, LocalStore) { + '/common/common-realtime.js', +], function (Crypt, FO, Hash, Realtime) { var exp = {}; var getType = function (el) { @@ -86,7 +86,7 @@ define([ exp.anonDriveIntoUser = function (proxyData, fsHash, cb) { // Make sure we have an FS_hash and we don't use it, otherwise just stop the migration and cb - if (!fsHash || !LocalStore.isLoggedIn()) { + if (!fsHash || !proxyData.loggedIn) { if (typeof(cb) === "function") { return void cb(); } } // Get the content of FS_hash and then merge the objects, remove the migration key and cb @@ -105,11 +105,11 @@ define([ if (parsed) { var proxy = proxyData.proxy; var oldFo = FO.init(parsed.drive, { - loggedIn: LocalStore.isLoggedIn() + loggedIn: proxyData.loggedIn }); var onMigrated = function () { oldFo.fixFiles(); - var newFo = proxyData.fo; + var newFo = proxyData.userObject; var oldRecentPads = parsed.drive[newFo.FILES_DATA]; var newRecentPads = proxy.drive[newFo.FILES_DATA]; var oldFiles = oldFo.getFiles([newFo.FILES_DATA]); @@ -154,7 +154,9 @@ define([ proxy.FS_hashes = []; } proxy.FS_hashes.push(fsHash); - if (typeof(cb) === "function") { cb(); } + if (typeof(cb) === "function") { + Realtime.whenRealtimeSyncs(proxyData.realtime, cb); + } }; oldFo.migrate(onMigrated); return; diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js new file mode 100644 index 000000000..46e8ba2a9 --- /dev/null +++ b/www/common/outer/async-store.js @@ -0,0 +1,766 @@ +define([ + '/common/userObject.js', + '/common/migrate-user-object.js', + '/common/common-hash.js', + '/common/common-util.js', + '/common/common-constants.js', + '/common/common-feedback.js', + '/common/common-realtime.js', + '/common/outer/network-config.js', + + '/bower_components/chainpad-crypto/crypto.js?v=0.1.5', + '/bower_components/chainpad/chainpad.dist.js', + '/bower_components/chainpad-listmap/chainpad-listmap.js', +], function (UserObject, Migrate, Hash, Util, Constants, Feedback, Realtime, NetConfig, + Crypto, ChainPad, Listmap) { + var Store = {}; + + var postMessage = function (cmd, data, cb) {}; + var tryParsing = function (x) { + try { return JSON.parse(x); } + catch (e) { + console.error(e); + return null; + } + }; + + + var storeHash; + + var store = {}; + + + var onSync = function (cb) { + Realtime.whenRealtimeSyncs(store.realtime, cb); + }; + + + Store.get = function (key, cb) { + cb({result: Util.find(store.proxy, key)}); + }; + Store.set = function (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'}); } + obj[key] = data.value; + onSync(cb); + }; + + 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'; + }; + + var getUserChannelList = function () { + // start with your userHash... + var userHash = storeHash; + if (!userHash) { return null; } + + var userParsedHash = Hash.parseTypeHash('drive', userHash); + var userChannel = userParsedHash && userParsedHash.channel; + if (!userChannel) { return null; } + + var list = store.userObject.getFiles([store.userObject.FILES_DATA]).map(function (id) { + return Hash.hrefToHexChannelId(store.userObject.getFileData(id).href); + }) + .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; + if (profileChan) { list.push(profileChan); } + var avatarChan = profile.avatar ? Hash.hrefToHexChannelId(profile.avatar) : null; + if (avatarChan) { list.push(avatarChan); } + } + + // TODO + /*if (store.proxy.friends) { + var fList = Messaging.getFriendChannelsList(common); + list = list.concat(fList); + }*/ + + list.push(Util.base64ToHex(userChannel)); + list.sort(); + + return list; + }; + + var getCanonicalChannelList = function () { + return Util.deduplicateString(getUserChannelList()).sort(); + }; + + ////////////////////////////////////////////////////////////////// + /////////////////////// RPC ////////////////////////////////////// + ////////////////////////////////////////////////////////////////// + + Store.arePinsSynced = function (cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + + var list = getCanonicalChannelList(); + var local = Hash.hashChannelList(list); + store.rpc.getServerHash(function (e, hash) { + if (e) { return void cb({error: e}); } + cb({synced: hash === local}); + }); + }; + + Store.resetPins = function (data, cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + + var list = getCanonicalChannelList(); + store.rpc.reset(list, function (e, hash) { + if (e) { return void cb({error: e}); } + cb({hash: hash}); + }); + }; + + Store.pinPads = function (data, cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + if (typeof(cb) !== 'function') { + console.error('expected a callback'); + } + + store.rpc.pin(data.pads, function (e, hash) { + if (e) { return void cb({error: e}); } + cb({hash: hash}); + }); + }; + + Store.unpinPads = function (data, cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + + store.rpc.unpin(data.pads, function (e, hash) { + if (e) { return void cb({error: e}); } + cb({hash: hash}); + }); + }; + + var account = {}; + + Store.getPinnedUsage = function (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 (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 (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 (data, cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + rpc.clearOwnedChannel(data.channel, cb); + }; + + Store.uploadComplete = function (data, cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + rpc.uploadComplete(cb); + }; + + Store.uploadStatus = function (data, cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + rpc.uploadStatus(data.size, cb); + }; + + Store.uploadCancel = function (data, cb) { + if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); } + rpc.uploadCancel(cb); + }; + + Store.initRpc = function (data, cb) { + 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; + + common.getPinLimit(function (e, limit, plan, note) { + if (e) { return void console.error(e); } + common.account.limit = limit; + localStorage.plan = common.account.plan = plan; + common.account.note = note; + cb(); + }); + + common.arePinsSynced(function (err, yes) { + if (!yes) { + common.resetPins(function (err) { + if (err) { return console.error(err); } + console.log('RESET DONE'); + }); + } + }); + }); + }); + }; + + ////////////////////////////////////////////////////////////////// + ////////////////// ANON RPC ////////////////////////////////////// + ////////////////////////////////////////////////////////////////// + Store.anonRpcMsg = function (data, cb) { + if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); } + store.anon_rpc.send(data.msg, data.data, cb); + }; + + Store.getFileSize = function (data, cb) { + if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); } + + var channelId = Hash.hrefToHexChannelId(data.href); + 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.getMultipleFileSize = function (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.initAnonRpc = function (data, 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(); + }); + }); + }; + + + + ////////////////////////////////////////////////////////////////// + /////////////////////// Store //////////////////////////////////// + ////////////////////////////////////////////////////////////////// + + // Get the metadata for sframe-common-outer + Store.getMetadata = function (data, cb) { + 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, + netfluxId: store.network.webChannels[0].myID + }, + // "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: !((store.proxy.settings || {}).general || {}).disableThumbnails + } + }; + cb(JSON.parse(JSON.stringify(metadata))); + }; + + // Reset the drive part of the userObject (from settings) + Store.resetDrive = function (data, cb) { + store.proxy.drive = store.fo.getStructure(); + onSync(cb); + }; + + /** + * add a "What is CryptPad?" pad in the drive + * data + * - driveReadme + * - driveReadmeTitle + */ + Store.createReadme = function (data, cb) { + require(['/common/cryptget.js'], function (Crypt) { + var hash = Hash.createRandomHash(); + 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 fileData = { + href: href, + title: data.driveReadmeTitle, + atime: +new Date(), + ctime: +new Date() + }; + store.userObject.pushData(fileData, function (e, id) { + if (e) { + return void cb({ error: "Error while creating the default pad:"+ e}); + } + store.userObject.add(id); + onSync(cb); + }); + }); + }); + }; + + + /** + * Merge the anonymous drive into the user drive at registration + * data + * - anonHash + */ + Store.migrateAnonDrive = function (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'); + return { + obj: storeObj.settings, + key: attr + }; + } + if (!Array.isArray(attr)) { throw new Error("Attribute must be string or array"); } + if (attr.length === 0) { throw new Error("Attribute can't be empty"); } + var obj = storeObj.settings; + attr.forEach(function (el, i) { + if (i === attr.length-1) { return; } + if (!obj[el]) { + obj[el] = {}; + } + else if (typeof obj[el] !== "object") { throw new Error("Wrong attribute"); } + obj = obj[el]; + }); + return { + obj: obj, + key: attr[attr.length-1] + }; + }; + + // Set the display name (username) in the proxy + Store.setDisplayName = function (value, cb) { + store.proxy[Constants.displayNameKey] = value; + onSync(cb); + }; + + /** + * Settings & pad attributes + * data + * - href (String) + * - attr (Array) + * - value (String) + */ + Store.setPadAttribute = function (data, cb) { + store.userObject.setPadAttribute(data.href, date.attr, data.value, function () { + onSync(cb); + }); + }; + Store.getPadAttribute = function (data, cb) { + filesOp.getPadAttribute(data.href, data.attr, function (err, val) { + if (err) { return void cb({error: err}); } + cb(val); + }); + }; + Store.setAttribute = function (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) { + var object; + try { + object = getAttributeObject(data.attr); + } catch (e) { return void cb({error: e}); } + cb(object.obj[object.key]); + }; + + // Tags + Store.listAllTags = function (data, cb) { + var all = []; + var files = Util.find(store.proxy, ['drive', 'filesData']); + + if (typeof(files) !== 'object') { return cb({error: 'invalid_drive'}); } + Object.keys(files).forEach(function (k) { + var file = files[k]; + if (!Array.isArray(file.tags)) { return; } + file.tags.forEach(function (tag) { + if (all.indexOf(tag) === -1) { all.push(tag); } + }); + }); + cb(all); + }; + + var makePad = common.makePad = function (href, title) { + var now = +new Date(); + return { + href: href, + atime: now, + ctime: now, + title: title || Hash.getDefaultName(Hash.parsePadUrl(href)), + }; + }; + + Store.addPad = function (data, cb) { + if (!data.href) { return void cb({error:'NO_HREF'}); } + var pad = makePad(data.href, data.title); + store.userObject.pushData(pad, function (e, id) { + if (e) { return void cb({error: "Error while adding a template:"+ e}); } + store.userObject.add(id, ['template']); + onSync(cb); + }); + }; + + // Templates + Store.getTemplates = function (data, cb) { + var templateFiles = filesOp.getFiles(['template']); + var res = []; + templateFiles.forEach(function (f) { + var data = filesOp.getFileData(f); + res.push(JSON.parse(JSON.stringify(data))); + }); + return res; + }; + + // Pads + Store.moveToTrash = function (data, cb) { + var href = Hash.getRelativeHref(data.href); + store.userObject.forget(href); + onSync(cb); + }; + Store.setPadTitle = function (data, cb) { + var title = data.title; + var href = data.href; + var p = Hash.parsePadUrl(href); + var h = p.hashData; + + 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 = recent[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; } + + 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 === 1 && h2.version === 1 && + h.channel === h2.channel) { + // 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; + } + } + + if (shouldUpdate) { + contains = true; + pad.atime = +new Date(); + pad.title = title; + + // If the href is different, it means we have a stronger one + if (href !== pad.href) { isStronger = true; } + pad.href = href; + } + } + + 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(obj.n); + } + + // Add the pad if it does not exist in our drive + if (!contains) { + Store.addPad({ + href: href, + title: title + }, cb); + return; + } + onSync(cb); + }; + + // Filepicker app + Store.getSecureFilesList = function (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); + }; + + // Get hashes for the share button + common.getStrongerHash = function (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, allPads); + if (stronger) { + var parsed2 = Hash.parsePadUrl(stronger); + return void cb(parsed2.hash); + } + cb(); + }; + + + var onReady = function (returned, cb) { + var proxy = store.proxy; + var userObject = store.userObject = UserObject.init(proxy.drive, { + pinPads: function (pads, cb) { Store.pinPads({pads: pads}, cb); }, + loggedIn: store.loggedIn + }); + var todo = function () { + fo.fixFiles(); + + Migrate(proxy, Cryptpad); + + var requestLogin = function () { + postMessage("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. */ + + // 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[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 (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 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; } + postMessage("UPDATE_METADATA"); + }); + proxy.on('change', ['profile'], function () { + // Trigger userlist update when the avatar has changed + postMessage("UPDATE_METADATA"); + }); + proxy.on('change', ['friends'], function () { + // Trigger userlist update when the friendlist has changed + postMessage("UPDATE_METADATA"); + }); + proxy.on('change', [Constants.tokenKey], function () { + postMessage("UPDATE_TOKEN", { data: proxy[Constants.tokenKey] }); + }); + }; + userObject.migrate(todo); + }; + + var connect = function (data, cb) { + var hash = data.userHash || data.anonHash || Hash.createRandomHash(); + storeHash = hash; + if (!hash) { + throw new Error('[Store.init] Unable to find or create a drive hash. Aborting...'); + } + 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 = Listmap.create(listmapConfig); + store.proxy = rt.proxy, + store.realtime = rt.realtime; + store.network = rt.network; + store.loggedIn = typeof(data.userHash) !== "undefined"; + + var returned = {}; + rt.proxy.on('create', function (info) { + exp.info = info; + if (!data.userHash) { + returned.anonHash = Hash.getEditHashFromKeys(info.channel, secret.keys); + } + }).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] = []; + } + // Drive already exist: return the existing drive, don't load data from legacy store + onReady(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(); + } + }); + }; + + /** + * 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 (data, callback) { + if (initialized) { + return void callback({ + error: 'ALREADY_INIT' + }); + } + initialized = true; + postMessage = function (cmd, data, cb) { + setTimeout(function () { + data.query(cmd, data, cb); // TODO temporary, will be rzplaced by webworker channel + }); + }; + + connect(data, function (ret) { + if (Object.keys(store.proxy).length === 1) { + Feedback.send("FIRST_APP_USE", true); + } + + callback(ret); + }); + }; + + Store.disconnect = function () { + if (!store.network) { return; } + store.network.disconnect(); + }; + return Store; +}); diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js new file mode 100644 index 000000000..5a49f31c4 --- /dev/null +++ b/www/common/outer/store-rpc.js @@ -0,0 +1,130 @@ +define([ + '/common/outer/async-store.js' +], function (Store) { + var Rpc = {}; + + Rpc.query = function (cmd, data, cb) { + 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 'UPLOAD_COMPLETE': { + Store.uploadComplete(data, cb); break; + } + case 'UPLOAD_STATUS': { + Store.uploadStatus(data, cb); break; + } + case 'UPLOAD_CANCEL': { + Store.uploadCancel(data, cb); break; + } + case 'ARE_PINS_SYNCED': { + Store.arePinsSynced(data, cb); break; + } + case 'RESET_PINS': { + Store.resetPins(data, cb); break; + } + case 'PIN_PADS': { + Store.pinPads(data, cb); break; + } + case 'UNPIN_PADS': { + Store.unpinPads(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_STRONGER_HASH': { + Store.getStrongerHash(data, cb); break; + } + default: { + + break; + } + } + + }; + + return Rpc; +}); + diff --git a/www/common/pinpad.js b/www/common/pinpad.js index ebece2626..07f55b5a2 100644 --- a/www/common/pinpad.js +++ b/www/common/pinpad.js @@ -3,13 +3,13 @@ define([ ], function (Rpc) { var create = function (network, proxy, cb) { if (!network) { - window.setTimeout(function () { + self.setTimeout(function () { cb('INVALID_NETWORK'); }); return; } if (!proxy) { - window.setTimeout(function () { + self.setTimeout(function () { cb('INVALID_PROXY'); }); return; @@ -19,7 +19,7 @@ define([ var edPublic = proxy.edPublic; if (!(edPrivate && edPublic)) { - window.setTimeout(function () { + self.setTimeout(function () { cb('INVALID_KEYS'); }); return; @@ -39,7 +39,7 @@ define([ // you can ask the server to pin a particular channel for you exp.pin = function (channels, cb) { if (!Array.isArray(channels)) { - window.setTimeout(function () { + self.setTimeout(function () { cb('[TypeError] pin expects an array'); }); return; @@ -50,7 +50,7 @@ define([ // you can also ask to unpin a particular channel exp.unpin = function (channels, cb) { if (!Array.isArray(channels)) { - window.setTimeout(function () { + self.setTimeout(function () { cb('[TypeError] pin expects an array'); }); return; @@ -71,7 +71,7 @@ define([ // if local and remote hashes don't match, send a reset exp.reset = function (channels, cb) { if (!Array.isArray(channels)) { - window.setTimeout(function () { + self.setTimeout(function () { cb('[TypeError] pin expects an array'); }); return; @@ -163,7 +163,7 @@ define([ exp.uploadStatus = function (size, cb) { if (typeof(size) !== 'number') { - return void window.setTimeout(function () { + return void self.setTimeout(function () { cb('INVALID_SIZE'); }); } diff --git a/www/common/rpc.js b/www/common/rpc.js index 9f04d7663..929be4795 100644 --- a/www/common/rpc.js +++ b/www/common/rpc.js @@ -2,7 +2,7 @@ define([ '/common/common-util.js', '/bower_components/tweetnacl/nacl-fast.min.js', ], function (Util) { - var Nacl = window.nacl; + var Nacl = self.nacl; var uid = Util.uid; var signMsg = function (data, signKey) { @@ -140,7 +140,7 @@ types of messages: var send = ctx.send = function (type, msg, cb) { if (!ctx.connected && type !== 'COOKIE') { - return void window.setTimeout(function () { + return void self.setTimeout(function () { cb('DISCONNECTED'); }); } @@ -185,7 +185,7 @@ types of messages: send.unauthenticated = function (type, msg, cb) { if (!ctx.connected) { - return void window.setTimeout(function () { + return void self.setTimeout(function () { cb('DISCONNECTED'); }); } @@ -276,7 +276,7 @@ types of messages: var send = ctx.send = function (type, msg, cb) { if (!ctx.connected) { - return void window.setTimeout(function () { + return void self.setTimeout(function () { cb('DISCONNECTED'); }); } diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 3e6c7c84c..c9eeb009e 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -104,10 +104,16 @@ define([ }); }); - secret = cfg.getSecrets ? cfg.getSecrets(Cryptpad, Utils) : Utils.Hash.getSecrets(); - if (!secret.channel) { - // New pad: create a new random channel id - secret.channel = Utils.Hash.createChannelId(); + if (cfg.getSecrets) { + cfg.getSecrets(Cryptpad, Utils, waitFor(function (err, s) { + secret = s; + })); + } else { + secret = Utils.Hash.getSecrets(); + if (!secret.channel) { + // New pad: create a new random channel id + secret.channel = Utils.Hash.createChannelId(); + } } Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; })); @@ -120,54 +126,46 @@ define([ var proxy = Cryptpad.getProxy(); var updateMeta = function () { //console.log('EV_METADATA_UPDATE'); - var name; + var metaObj, isTemplate; nThen(function (waitFor) { - Cryptpad.getLastName(waitFor(function (err, n) { + Cryptpad.getMetadata(waitFor(function (err, m) { + if (err) { console.log(err); } + metaObj = m; + })); + Cryptpad.isTemplate(window.location.href, waitFor(function (err, t) { if (err) { console.log(err); } - name = n; + isTemplate = t; })); }).nThen(function (/*waitFor*/) { - var metaObj = { - doc: { - defaultTitle: defaultTitle, - type: parsed.type - }, - user: { - name: name, - uid: Cryptpad.getUid(), - avatar: Cryptpad.getAvatarUrl(), - profile: Cryptpad.getProfileUrl(), - curvePublic: proxy.curvePublic, - netfluxId: Cryptpad.getNetwork().webChannels[0].myID, - }, - priv: { - edPublic: proxy.edPublic, - accountName: Utils.LocalStore.getAccountName(), - origin: window.location.origin, - pathname: window.location.pathname, - fileHost: ApiConfig.fileHost, - readOnly: readOnly, - availableHashes: hashes, - isTemplate: Cryptpad.isTemplate(window.location.href), - feedbackAllowed: Utils.Feedback.state, - friends: proxy.friends || {}, - settings: proxy.settings || {}, - isPresent: parsed.hashData && parsed.hashData.present, - isEmbed: parsed.hashData && parsed.hashData.embed, - thumbnails: !((proxy.settings || {}).general || {}).disableThumbnails, - accounts: { - donateURL: Cryptpad.donateURL, - upgradeURL: Cryptpad.upgradeURL - } + metaObj.doc: { + defaultTitle: defaultTitle, + type: parsed.type + }; + var additionalPriv = { + accountName: Utils.LocalStore.getAccountName(), + origin: window.location.origin, + pathname: window.location.pathname, + fileHost: ApiConfig.fileHost, + readOnly: readOnly, + availableHashes: hashes, + isTemplate: isTemplate, + feedbackAllowed: Utils.Feedback.state, + isPresent: parsed.hashData && parsed.hashData.present, + isEmbed: parsed.hashData && parsed.hashData.embed, + accounts: { + donateURL: Cryptpad.donateURL, + upgradeURL: Cryptpad.upgradeURL } }; + for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; } + if (cfg.addData) { cfg.addData(metaObj.priv, Cryptpad); } sframeChan.event('EV_METADATA_UPDATE', metaObj); }); }; - Cryptpad.onDisplayNameChanged(updateMeta); + Cryptpad.onMetadataChanged(updateMeta); sframeChan.onReg('EV_METADATA_UPDATE', updateMeta); proxy.on('change', 'settings', updateMeta); @@ -445,8 +443,9 @@ define([ Cryptpad.useTemplate(href, Cryptget, cb); }); sframeChan.on('Q_TEMPLATE_EXIST', function (type, cb) { - var hasTemplate = Cryptpad.listTemplates(type).length > 0; - cb(hasTemplate); + Cryptpad.listTemplates(type, function (err, templates) { + cb(templates.length > 0); + }); }); sframeChan.on('EV_GOTO_URL', function (url) { diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index f50ff71da..1d21acb92 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -373,10 +373,6 @@ define([ }); }); - ctx.sframeChan.on('EV_RT_CONNECT', function () { CommonRealtime.setConnectionState(true); }); - ctx.sframeChan.on('EV_RT_DISCONNECT', function () { CommonRealtime.setConnectionState(false); }); - - ctx.sframeChan.on('Q_INCOMING_FRIEND_REQUEST', function (confirmMsg, cb) { UI.confirm(confirmMsg, cb, null, true); }); diff --git a/www/common/userObject.js b/www/common/userObject.js index 58e45e8ee..88c354b5b 100644 --- a/www/common/userObject.js +++ b/www/common/userObject.js @@ -21,13 +21,13 @@ define([ module.init = function (files, config) { var exp = {}; - var Cryptpad = config.Cryptpad; + var pinPads = config.pinPads; var loggedIn = config.loggedIn; var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Constants.storageKey; var OLD_FILES_DATA = module.OLD_FILES_DATA = exp.OLD_FILES_DATA = Constants.oldStorageKey; - var NEW_FOLDER_NAME = Messages.fm_newFolder; - var NEW_FILE_NAME = Messages.fm_newFile; + var NEW_FOLDER_NAME = Messages.fm_newFolder || 'New folder'; + var NEW_FILE_NAME = Messages.fm_newFile || 'New file'; exp.ROOT = ROOT; exp.UNSORTED = UNSORTED; @@ -488,7 +488,7 @@ define([ // FILES DATA exp.pushData = function (data, cb) { // TODO: can only be called from outside atm - if (!Cryptpad) { return; } + if (!pinPads) { return; } if (typeof cb !== "function") { cb = function () {}; } var todo = function () { var id = Util.createRandomInteger(); @@ -498,7 +498,7 @@ define([ if (!loggedIn || !AppConfig.enablePinning || config.testMode) { return void todo(); } - Cryptpad.pinPads([Hash.hrefToHexChannelId(data.href)], function (e) { + pinPads([Hash.hrefToHexChannelId(data.href)], function (e) { if (e) { return void cb(e); } todo(); }); diff --git a/www/drive/tests.js b/www/drive/tests.js index 23ed2172f..222d1b905 100644 --- a/www/drive/tests.js +++ b/www/drive/tests.js @@ -70,7 +70,7 @@ define([ module.test = function (assert) { var config = { - Cryptpad: Cryptpad, + pinPads: Cryptpad.pinPads, workgroup: false, testMode: true, loggedIn: false diff --git a/www/filepicker/main.js b/www/filepicker/main.js index d628b401c..738e40773 100644 --- a/www/filepicker/main.js +++ b/www/filepicker/main.js @@ -49,36 +49,27 @@ define([ var proxy = Cryptpad.getProxy(); var updateMeta = function () { //console.log('EV_METADATA_UPDATE'); - var name; + var metaObj; nThen(function (waitFor) { - Cryptpad.getLastName(waitFor(function (err, n) { + Cryptpad.getMetadata(waitFor(function (err, n) { if (err) { console.log(err); } - name = n; + metaObj = n; })); }).nThen(function (/*waitFor*/) { - sframeChan.event('EV_METADATA_UPDATE', { - doc: {}, - user: { - name: name, - uid: Cryptpad.getUid(), - avatar: Cryptpad.getAvatarUrl(), - profile: Cryptpad.getProfileUrl(), - curvePublic: proxy.curvePublic, - netfluxId: Cryptpad.getNetwork().webChannels[0].myID, - }, - priv: { - accountName: Utils.LocalStore.getAccountName(), - origin: window.location.origin, - pathname: window.location.pathname, - feedbackAllowed: Utils.Feedback.state, - friends: proxy.friends || {}, - settings: proxy.settings || {}, - types: config.types - } - }); + metaObj.doc: {}; + var additionalPriv = { + accountName: Utils.LocalStore.getAccountName(), + origin: window.location.origin, + pathname: window.location.pathname, + feedbackAllowed: Utils.Feedback.state, + types: config.types + }; + for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; } + + sframeChan.event('EV_METADATA_UPDATE', metaObj); }); }; - Cryptpad.onDisplayNameChanged(updateMeta); + Cryptpad.onMetadataChanged(updateMeta); sframeChan.onReg('EV_METADATA_UPDATE', updateMeta); proxy.on('change', 'settings', updateMeta); diff --git a/www/profile/inner.js b/www/profile/inner.js index 9b48e2ca2..2728dc15e 100644 --- a/www/profile/inner.js +++ b/www/profile/inner.js @@ -395,15 +395,6 @@ define([ var onReady = function () { APP.$container.find('#'+CREATE_ID).remove(); - /*var obj = APP.lm && APP.lm.proxy; - if (!APP.readOnly) { - var pubKeys = Cryptpad.getPublicKeys(); - if (pubKeys && pubKeys.curve) { - obj.curveKey = pubKeys.curve; - obj.edKey = pubKeys.ed; - } - }*/ - if (!APP.initialized) { var $header = $('
', {id: HEADER_ID}).appendTo(APP.$rightside); addAvatar($header); diff --git a/www/profile/main.js b/www/profile/main.js index 195dbdc6b..bdee640bf 100644 --- a/www/profile/main.js +++ b/www/profile/main.js @@ -36,34 +36,41 @@ define([ }; window.addEventListener('message', onMsg); }).nThen(function (/*waitFor*/) { - var getSecrets = function (Cryptpad, Utils) { + var getSecrets = function (Cryptpad, Utils, cb) { var Hash = Utils.Hash; // 1st case: visiting someone else's profile with hash in the URL if (window.location.hash) { - return Hash.getSecrets('profile', window.location.hash.slice(1)); + return void cb(null, Hash.getSecrets('profile', window.location.hash.slice(1))); } - // 2nd case: visiting our own existing profile - var obj = Cryptpad.getProxy(); - if (obj.profile && obj.profile.view && obj.profile.edit) { - return Hash.getSecrets('profile', obj.profile.edit); - } - // 3rd case: profile creation (create a new random hash, store it later if needed) - if (!Utils.LocalStore.isLoggedIn()) { return; } - var hash = Hash.createRandomHash(); - var secret = Hash.getSecrets('profile', hash); - Cryptpad.pinPads([secret.channel], function (e) { - if (e) { - if (e === 'E_OVER_LIMIT') { - // TODO - } - return; - //return void UI.log(Messages._getKey('profile_error', [e])) // TODO + var editHash; + nThen(function (waitFor) { + // 2nd case: visiting our own existing profile + Cryptpad.getProfileEditUrl(waitFor(function (hash) { + editHash = hash; + })); + }).nThen(function () { + if (!editHash) { + return void cb(null, Hash.getSecrets('profile', editHash)); } - obj.profile = {}; - obj.profile.edit = Utils.Hash.getEditHashFromKeys(secret.channel, secret.keys); - obj.profile.view = Utils.Hash.getViewHashFromKeys(secret.channel, secret.keys); + // 3rd case: profile creation (create a new random hash, store it later if needed) + if (!Utils.LocalStore.isLoggedIn()) { return void cb(); } + var hash = Hash.createRandomHash(); + var secret = Hash.getSecrets('profile', hash); + Cryptpad.pinPads([secret.channel], function (e) { + if (e) { + if (e === 'E_OVER_LIMIT') { + // TODO + } + return; + //return void UI.log(Messages._getKey('profile_error', [e])) // TODO + } + var profile = {}; + profile.edit = Utils.Hash.getEditHashFromKeys(secret.channel, secret.keys); + profile.view = Utils.Hash.getViewHashFromKeys(secret.channel, secret.keys); + Cryptpad.setNewProfile(profile); + }); + cb(null, secret); }); - return secret; }; var addRpc = function (sframeChan, Cryptpad, Utils) { // Adding a new avatar from the profile: pin it and store it in the object diff --git a/www/settings/main.js b/www/settings/main.js index 958dcda45..7075ccc8c 100644 --- a/www/settings/main.js +++ b/www/settings/main.js @@ -43,7 +43,7 @@ define([ }); }); sframeChan.on('Q_SETTINGS_DRIVE_GET', function (d, cb) { - cb(Cryptpad.getProxy()); + Cryptpad.getUserObject(cb); }); sframeChan.on('Q_SETTINGS_DRIVE_SET', function (data, cb) { var sjson = JSON.stringify(data); @@ -57,26 +57,13 @@ define([ }); }); sframeChan.on('Q_SETTINGS_DRIVE_RESET', function (data, cb) { - var proxy = Cryptpad.getProxy(); - var realtime = Cryptpad.getRealtime(); - proxy.drive = Cryptpad.getStore().getEmptyObject(); - Utils.Realtime.whenRealtimeSyncs(realtime, cb); + Cryptpad.resetDrive(cb); }); sframeChan.on('Q_SETTINGS_LOGOUT', function (data, cb) { - var proxy = Cryptpad.getProxy(); - var realtime = Cryptpad.getRealtime(); - var token = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER); - localStorage.setItem('loginToken', token); - proxy.loginToken = token; - Utils.Realtime.whenRealtimeSyncs(realtime, cb); + Cryptpad.logoutFromAll(cb); }); sframeChan.on('Q_SETTINGS_IMPORT_LOCAL', function (data, cb) { - var proxyData = Cryptpad.getStore().getProxy(); - require([ - '/common/mergeDrive.js', - ], function (Merge) { - Merge.anonDriveIntoUser(proxyData, Utils.LocalStore.getFSHash(), cb); - }); + Cryptpad.mergeAnonDrive(cb); }); }; SFCommonO.start({