define([ '/api/config', '/customize/messages.js', '/common/fsStore.js', '/common/common-util.js', '/common/common-hash.js', '/bower_components/alertifyjs/dist/js/alertify.js', '/common/clipboard.js', '/common/pinpad.js', /* TODO load pinpad dynamically only after you know that it will be needed */ '/customize/application_config.js', '/bower_components/jquery/dist/jquery.min.js', ], function (Config, Messages, Store, Util, Hash, Alertify, Clipboard, Pinpad, AppConfig) { /* This file exposes functionality which is specific to Cryptpad, but not to any particular pad type. This includes functions for committing metadata about pads to your local storage for future use and improved usability. Additionally, there is some basic functionality for import/export. */ var $ = window.jQuery; var common = window.Cryptpad = { Messages: Messages, Alertify: Alertify, Clipboard: Clipboard }; // constants var userHashKey = common.userHashKey = 'User_hash'; var userNameKey = common.userNameKey = 'User_name'; var fileHashKey = common.fileHashKey = 'FS_hash'; var displayNameKey = common.displayNameKey = 'cryptpad.username'; var LOADING = 'loading'; var newPadNameKey = common.newPadNameKey = "newPadName"; var newPadPathKey = common.newPadPathKey = "newPadPath"; var storageKey = common.storageKey = 'CryptPad_RECENTPADS'; var PINNING_ENABLED = AppConfig.enablePinning; var store; var rpc; // import common utilities for export var find = common.find = Util.find; var fixHTML = common.fixHTML = Util.fixHTML; var hexToBase64 = common.hexToBase64 = Util.hexToBase64; var base64ToHex = common.base64ToHex = Util.base64ToHex; var deduplicateString = common.deduplicateString = Util.deduplicateString; var uint8ArrayToHex = common.uint8ArrayToHex = Util.uint8ArrayToHex; var replaceHash = common.replaceHash = Util.replaceHash; var getHash = common.getHash = Util.getHash; var fixFileName = common.fixFileName = Util.fixFileName; // import hash utilities for export var createRandomHash = Hash.createRandomHash; var parsePadUrl = common.parsePadUrl = Hash.parsePadUrl; var isNotStrongestStored = common.isNotStrongestStored = Hash.isNotStrongestStored; var hrefToHexChannelId = common.hrefToHexChannelId = Hash.hrefToHexChannelId; var parseHash = common.parseHash = Hash.parseHash; var getRelativeHref = common.getRelativeHref = Hash.getRelativeHref; common.getEditHashFromKeys = Hash.getEditHashFromKeys; common.getViewHashFromKeys = Hash.getViewHashFromKeys; common.getSecrets = Hash.getSecrets; common.getHashes = Hash.getHashes; common.createChannelId = Hash.createChannelId; common.findWeaker = Hash.findWeaker; common.findStronger = Hash.findStronger; 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; } }; var getNetwork = common.getNetwork = function () { if (store) { if (store.getProxy() && store.getProxy().info) { return store.getProxy().info.network; } } return; }; var feedback = common.feedback = function (action) { if (!action) { return; } try { if (!getStore().getProxy().proxy.allowUserFeedback) { return; } } catch (e) { return void console.error(e); } var href = '/common/feedback.html?' + action + '=' + (+new Date()); $.ajax({ type: "HEAD", url: href, }); }; var reportAppUsage = common.reportAppUsage = function () { var pattern = window.location.pathname.split('/') .filter(function (x) { return x; }).join('.'); feedback(pattern); }; var getUid = common.getUid = function () { if (store) { if (store.getProxy() && store.getProxy().proxy) { return store.getProxy().proxy.uid; } } }; var getRealtime = common.getRealtime = function () { if (store) { if (store.getProxy() && store.getProxy().info) { return store.getProxy().info.realtime; } } return; }; var whenRealtimeSyncs = common.whenRealtimeSyncs = function (realtime, cb) { realtime.sync(); window.setTimeout(function () { if (realtime.getAuthDoc() === realtime.getUserDoc()) { return void cb(); } realtime.onSettle(function () { cb(); }); }, 0); }; var getWebsocketURL = common.getWebsocketURL = function () { if (!Config.websocketPath) { return Config.websocketURL; } var path = Config.websocketPath; if (/^ws{1,2}:\/\//.test(path)) { return path; } var protocol = window.location.protocol.replace(/http/, 'ws'); var host = window.location.host; var url = protocol + '//' + host + path; return url; }; var login = common.login = function (hash, name, cb) { if (!hash) { throw new Error('expected a user hash'); } if (!name) { throw new Error('expected a user name'); } localStorage.setItem(userHashKey, hash); localStorage.setItem(userNameKey, name); if (cb) { cb(); } }; var eraseTempSessionValues = common.eraseTempSessionValues = function () { // delete sessionStorage values that might have been left over // from the main page's /user redirect [ 'login', 'login_user', 'login_pass', 'login_rmb', 'register' ].forEach(function (k) { delete sessionStorage[k]; }); }; var logoutHandlers = []; var logout = common.logout = function (cb) { [ userNameKey, userHashKey, ].forEach(function (k) { sessionStorage.removeItem(k); localStorage.removeItem(k); delete localStorage[k]; delete sessionStorage[k]; }); // Make sure we have an FS_hash in localStorage before reloading all the tabs // so that we don't end up with tabs using different anon hashes if (!localStorage[fileHashKey]) { localStorage[fileHashKey] = common.createRandomHash(); } eraseTempSessionValues(); logoutHandlers.forEach(function (h) { if (typeof (h) === "function") { h(); } }); if (cb) { cb(); } }; var onLogout = common.onLogout = function (h) { if (typeof (h) !== "function") { return; } if (logoutHandlers.indexOf(h) !== -1) { return; } logoutHandlers.push(h); }; var getUserHash = common.getUserHash = function () { var hash; [sessionStorage, localStorage].some(function (s) { var h = s[userHashKey]; if (h) { return (hash = h); } }); return hash; }; var isLoggedIn = common.isLoggedIn = function () { return typeof getUserHash() === "string"; }; var isArray = common.isArray = $.isArray; var truncate = common.truncate = function (text, len) { if (typeof(text) === 'string' && text.length > len) { return text.slice(0, len) + '…'; } return text; }; /* * localStorage formatting */ /* the first time this gets called, your local storage will migrate to a new format. No more indices for values, everything is named now. * href * atime (access time) * title * ??? // what else can we put in here? */ var checkObjectData = function (pad) { if (!pad.ctime) { pad.ctime = pad.atime; } if (/^https*:\/\//.test(pad.href)) { pad.href = common.getRelativeHref(pad.href); } var parsed = common.parsePadUrl(pad.href); if (!parsed || !parsed.hash) { return; } if (!pad.title) { pad.title = common.getDefaultname(parsed); } return parsed.hash; }; // Migrate from legacy store (localStorage) var migrateRecentPads = common.migrateRecentPads = function (pads) { return pads.map(function (pad) { var hash; if (isArray(pad)) { var href = pad[0]; href.replace(/\#(.*)$/, function (a, h) { hash = h; }); return { href: pad[0], atime: pad[1], title: pad[2] || hash && hash.slice(0,8), ctime: pad[1], }; } else if (pad && typeof(pad) === 'object') { hash = checkObjectData(pad); if (!hash || !common.parseHash(hash)) { return; } return pad; } else { console.error("[Cryptpad.migrateRecentPads] pad had unexpected value"); console.log(pad); return; } }).filter(function (x) { return x; }); }; // Remove everything from RecentPads that is not an object and check the objects var checkRecentPads = common.checkRecentPads = function (pads) { pads.forEach(function (pad, i) { if (pad && typeof(pad) === 'object') { var hash = checkObjectData(pad); if (!hash || !common.parseHash(hash)) { return; } return pad; } console.error("[Cryptpad.migrateRecentPads] pad had unexpected value"); getStore().removeData(i); }); }; // Get the pads from localStorage to migrate them to the object store var getLegacyPads = common.getLegacyPads = function (cb) { require(['/customize/store.js'], function(Legacy) { Legacy.ready(function (err, legacy) { if (err) { cb(err, null); return; } legacy.get(storageKey, function (err2, recentPads) { if (err2) { cb(err2, null); return; } if (isArray(recentPads)) { cb(void 0, migrateRecentPads(recentPads)); return; } cb(void 0, []); }); }); }); }; var isNameAvailable = function (title, parsed, pads) { return !pads.some(function (pad) { // another pad is already using that title if (pad.title === title) { return true; } }); }; // Create untitled documents when no name is given var getDefaultName = common.getDefaultName = function (parsed) { var type = parsed.type; var untitledIndex = 1; var name = (Messages.type)[type] + ' - ' + new Date().toString().split(' ').slice(0,4).join(' '); return name; }; var isDefaultName = common.isDefaultName = function (parsed, title) { var name = getDefaultName(parsed); return title === name; }; var makePad = function (href, title) { var now = +new Date(); return { href: href, atime: now, ctime: now, title: title || window.location.hash.slice(1, 9), }; }; /* Sort pads according to how recently they were accessed */ var mostRecent = common.mostRecent = function (a, b) { return new Date(b.atime).getTime() - new Date(a.atime).getTime(); }; // STORAGE var setPadAttribute = common.setPadAttribute = function (attr, value, cb) { getStore().setDrive([getHash(), attr].join('.'), value, function (err, data) { cb(err, data); }); }; var setAttribute = common.setAttribute = function (attr, value, cb) { getStore().set(["cryptpad", attr].join('.'), value, function (err, data) { cb(err, data); }); }; var setLSAttribute = common.setLSAttribute = function (attr, value) { localStorage[attr] = value; }; // STORAGE var getPadAttribute = common.getPadAttribute = function (attr, cb) { getStore().getDrive([getHash(), attr].join('.'), function (err, data) { cb(err, data); }); }; var getAttribute = common.getAttribute = function (attr, cb) { getStore().get(["cryptpad", attr].join('.'), function (err, data) { cb(err, data); }); }; var getLSAttribute = common.getLSAttribute = function (attr) { return localStorage[attr]; }; // STORAGE - TEMPLATES var listTemplates = common.listTemplates = function (type) { var allTemplates = getStore().listTemplates(); if (!type) { return allTemplates; } var templates = allTemplates.filter(function (f) { var parsed = parsePadUrl(f.href); return parsed.type === type; }); return templates; }; var addTemplate = common.addTemplate = function (data) { getStore().pushData(data); getStore().addPad(data.href, ['template']); }; var isTemplate = common.isTemplate = function (href) { var rhref = getRelativeHref(href); var templates = listTemplates(); return templates.some(function (t) { return t.href === rhref; }); }; var selectTemplate = common.selectTemplate = function (type, rt, Crypt) { if (!AppConfig.enableTemplates) { return; } var temps = listTemplates(type); if (temps.length === 0) { return; } var $content = $('