define([ '/api/config', '/customize/messages.js', '/common/common-util.js', '/common/common-hash.js', '/common/common-messaging.js', '/common/common-constants.js', '/common/common-feedback.js', '/common/outer/local-store.js', '/common/outer/worker-channel.js', '/common/outer/login-block.js', '/customize/application_config.js', '/bower_components/nthen/index.js', ], function (Config, Messages, Util, Hash, Messaging, Constants, Feedback, LocalStore, Channel, Block, AppConfig, Nthen) { /* This file exposes functionality which is specific to Cryptpad, but not to any particular pad type. This includes functions for committing metadata about pads to your local storage for future use and improved usability. Additionally, there is some basic functionality for import/export. */ var urlArgs = Util.find(Config, ['requireConf', 'urlArgs']) || ''; var postMessage = function (/*cmd, data, cb*/) { /*setTimeout(function () { AStore.query(cmd, data, cb); });*/ console.error('NOT_READY'); }; 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 = { Messages: Messages, donateURL: 'https://accounts.cryptpad.fr/#/donate?on=' + origin, upgradeURL: 'https://accounts.cryptpad.fr/#/?on=' + origin, account: {}, }; var PINNING_ENABLED = AppConfig.enablePinning; // COMMON common.getLanguage = function () { return Messages._languageUsed; }; common.setLanguage = function (l, cb) { var LS_LANG = "CRYPTPAD_LANG"; localStorage.setItem(LS_LANG, l); cb(); }; // RESTRICTED // Settings only 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 && obj.error) { return void cb(obj.error); } cb(); }); }; // Settings and drive 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); }; // Settings common.deleteAccount = function (cb) { postMessage("DELETE_ACCOUNT", null, cb); }; // Drive common.userObjectCommand = function (data, cb) { postMessage("DRIVE_USEROBJECT", data, cb); }; common.drive = {}; common.drive.onLog = Util.mkEvent(); common.drive.onChange = Util.mkEvent(); common.drive.onRemove = Util.mkEvent(); // Profile common.getProfileEditUrl = function (cb) { postMessage("GET", ['profile', 'edit'], function (obj) { cb(obj); }); }; common.setNewProfile = function (profile) { postMessage("SET", { key: ['profile'], value: profile }, function () {}); }; common.setAvatar = function (data, cb) { var postData = { key: ['profile', 'avatar'] }; // If we don't have "data", it means we want to remove the avatar and we should not have a // "postData.value", even set to undefined (JSON.stringify transforms undefined to null) if (data) { postData.value = data; } postMessage("SET", postData, cb); }; // Todo common.getTodoHash = function (cb) { postMessage("GET", ['todo'], function (obj) { cb(obj); }); }; common.setTodoHash = function (hash) { postMessage("SET", { key: ['todo'], value: hash }, function () {}); }; // RPC common.pinPads = function (pads, cb) { postMessage("PIN_PADS", pads, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(null, obj.hash); }); }; common.unpinPads = function (pads, cb) { postMessage("UNPIN_PADS", pads, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(null, obj.hash); }); }; common.getPinnedUsage = function (cb) { postMessage("GET_PINNED_USAGE", null, function (obj) { if (obj.error) { return void cb(obj.error); } cb(null, obj.bytes); }); }; common.updatePinLimit = function (cb) { 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) { 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) { if (!LocalStore.isLoggedIn()) { return void cb(null, false); } var usage; var andThen = function (e, limit, plan) { if (e) { return void cb(e); } var data = {usage: usage, limit: limit, plan: plan}; if (usage > limit) { return void cb (null, true, data); } return void cb (null, false, data); }; var todo = function (e, used) { if (e) { return void cb(e); } usage = used; common.getPinLimit(andThen); }; common.getPinnedUsage(todo); }; common.clearOwnedChannel = function (channel, cb) { postMessage("CLEAR_OWNED_CHANNEL", channel, cb); }; common.removeOwnedChannel = function (channel, cb) { postMessage("REMOVE_OWNED_CHANNEL", channel, cb); }; common.getDeletedPads = function (cb) { postMessage("GET_DELETED_PADS", null, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(null, obj); }); }; common.uploadComplete = function (id, owned, cb) { postMessage("UPLOAD_COMPLETE", {id: id, owned: owned}, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(null, obj); }); }; common.uploadStatus = function (size, cb) { postMessage("UPLOAD_STATUS", {size: size}, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(null, obj); }); }; common.uploadCancel = function (size, cb) { postMessage("UPLOAD_CANCEL", {size: size}, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(null, obj); }); }; common.uploadChunk = function (data, cb) { postMessage("UPLOAD_CHUNK", {chunk: data}, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(null, obj); }); }; common.writeLoginBlock = function (data, cb) { postMessage('WRITE_LOGIN_BLOCK', data, function (obj) { cb(obj); }); }; common.removeLoginBlock = function (data, cb) { postMessage('REMOVE_LOGIN_BLOCK', data, function (obj) { cb(obj); }); }; // ANON RPC // SFRAME: talk to anon_rpc from the iframe common.anonRpcMsg = function (msg, data, cb) { if (!msg) { return; } postMessage("ANON_RPC_MESSAGE", { msg: msg, data: data }, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(null, obj); }); }; common.getFileSize = function (href, password, cb) { postMessage("GET_FILE_SIZE", {href: href, password: password}, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(undefined, obj.size); }); }; common.getMultipleFileSize = function (files, cb) { postMessage("GET_MULTIPLE_FILE_SIZE", {files:files}, function (obj) { if (obj.error) { return void cb(obj.error); } cb(undefined, obj.size); }); }; common.isNewChannel = function (href, password, cb) { postMessage('IS_NEW_CHANNEL', {href: href, password: password}, function (obj) { if (obj.error) { return void cb(obj.error); } if (!obj) { return void cb('INVALID_RESPONSE'); } cb(undefined, obj.isNew); }); }; // Store common.getMetadata = function (cb) { postMessage("GET_METADATA", null, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(null, obj); }); }; common.setDisplayName = function (value, cb) { postMessage("SET_DISPLAY_NAME", value, cb); }; common.setPadAttribute = function (attr, value, cb, href) { cb = cb || function () {}; href = Hash.getRelativeHref(href || window.location.href); postMessage("SET_PAD_ATTRIBUTE", { href: href, attr: attr, value: value }, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(); }); }; common.getPadAttribute = function (attr, cb, href) { href = Hash.getRelativeHref(href || window.location.href); 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) { cb = cb || function () {}; postMessage("SET_ATTRIBUTE", { attr: attr, value: value }, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(); }); }; common.getAttribute = function (attr, cb) { postMessage("GET_ATTRIBUTE", { attr: attr }, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(null, obj); }); }; // Tags common.resetTags = function (href, tags, cb) { // set pad attribute cb = cb || function () {}; if (!Array.isArray(tags)) { return void cb('INVALID_TAGS'); } 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'); } common.getPadAttribute('tags', function (e, tags) { if (e) { return void cb(e); } var newTags; if (!tags) { newTags = [tag]; } else if (tags.indexOf(tag) === -1) { newTags = tags.slice(); newTags.push(tag); } 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'); } common.getPadAttribute('tags', function (e, tags) { if (e) { return void cb(e); } if (!tags) { return void cb(); } var idx = tags.indexOf(tag); if (idx === -1) { return void 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; } common.getPadAttribute('tags', function (e, tags) { if (e) { return void cb(e); } cb(void 0, tags ? tags.slice() : []); }, href); }; common.listAllTags = function (cb) { postMessage("LIST_ALL_TAGS", null, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(void 0, obj); }); }; // STORAGE - TEMPLATES 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(null, obj); } var templates = obj.filter(function (f) { var parsed = Hash.parsePadUrl(f.href); return parsed.type === type; }); cb(null, templates); }); }; common.saveAsTemplate = function (Cryptput, data, cb) { var p = Hash.parsePadUrl(window.location.href); if (!p.type) { return; } // PPP: password for the new template? var hash = Hash.createRandomHash(p.type); var href = '/' + p.type + '/#' + hash; // PPP: add password as cryptput option Cryptput(hash, data.toSave, function (e) { if (e) { throw new Error(e); } postMessage("ADD_PAD", { href: href, title: data.title, path: ['template'] }, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(); }); }); }; common.isTemplate = function (href, cb) { var rhref = Hash.getRelativeHref(href); common.listTemplates(null, function (err, templates) { cb(void 0, templates.some(function (t) { return t.href === rhref; })); }); }; common.useTemplate = function (href, Crypt, cb, optsPut) { // opts is used to overrides options for chainpad-netflux in cryptput // it allows us to add owners and expiration time if it is a new file var parsed = Hash.parsePadUrl(href); var parsed2 = Hash.parsePadUrl(window.location.href); if(!parsed) { throw new Error("Cannot get template hash"); } postMessage("INCREMENT_TEMPLATE_USE", href); optsPut = optsPut || {}; var optsGet = {}; Nthen(function (waitFor) { if (parsed.hashData && parsed.hashData.password) { common.getPadAttribute('password', waitFor(function (err, password) { optsGet.password = password; }), href); } if (parsed2.hashData && parsed2.hashData.password && !optsPut.password) { common.getPadAttribute('password', waitFor(function (err, password) { optsPut.password = password; })); } }).nThen(function () { Crypt.get(parsed.hash, function (err, val) { if (err) { throw new Error(err); } try { // Try to fix the title before importing the template var parsed = JSON.parse(val); var meta; if (Array.isArray(parsed) && typeof(parsed[3]) === "object") { meta = parsed[3].metadata; // pad } else if (parsed.info) { meta = parsed.info; // poll } else { meta = parsed.metadata; } if (typeof(meta) === "object") { meta.defaultTitle = meta.title || meta.defaultTitle; delete meta.users; meta.title = ""; } val = JSON.stringify(parsed); } catch (e) { console.log("Can't fix template title", e); } Crypt.put(parsed2.hash, val, cb, optsPut); }, optsGet); }); }; // Forget button common.moveToTrash = function (cb, href) { href = href || window.location.href; postMessage("MOVE_TO_TRASH", { href: href }, cb); }; // When opening a new pad or renaming it, store the new title common.setPadTitle = function (data, cb) { if (!data || typeof (data) !== "object") { return cb ('Data is not an object'); } var href = data.href || window.location.href; var parsed = Hash.parsePadUrl(href); if (!parsed.hash) { return cb ('Invalid hash'); } data.href = parsed.getUrl({present: parsed.present}); if (typeof (data.title) !== "string") { return cb('Missing title'); } if (data.title.trim() === "") { data.title = Hash.getDefaultName(parsed); } if (common.initialPath) { if (!data.path) { data.path = common.initialPath; delete common.initialPath; } } postMessage("SET_PAD_TITLE", data, function (obj) { if (obj && obj.error) { console.log("unable to set pad title"); return void cb(obj.error); } cb(); }); }; // Needed for the secure filepicker app common.getSecureFilesList = function (query, cb) { postMessage("GET_SECURE_FILES_LIST", query, function (list) { cb(void 0, list); }); }; // Get a template href from its id common.getPadData = function (id, cb) { postMessage("GET_PAD_DATA", id, function (data) { cb(void 0, data); }); }; // Messaging (manage friends from the userlist) common.inviteFromUserlist = function (netfluxId, cb) { postMessage("INVITE_FROM_USERLIST", { netfluxId: netfluxId, href: window.location.href }, function (obj) { if (obj && obj.error) { return void cb(obj.error); } cb(); }); }; // Network common.onNetworkDisconnect = Util.mkEvent(); common.onNetworkReconnect = Util.mkEvent(); common.onNewVersionReconnect = Util.mkEvent(); // Messaging var messaging = common.messaging = {}; messaging.onFriendRequest = Util.mkEvent(); messaging.onFriendComplete = Util.mkEvent(); messaging.addHandlers = function (href) { postMessage("ADD_DIRECT_MESSAGE_HANDLERS", { href: href }); }; // Messenger var messenger = common.messenger = {}; messenger.getFriendList = function (cb) { postMessage("CONTACTS_GET_FRIEND_LIST", null, cb); }; messenger.getMyInfo = function (cb) { postMessage("CONTACTS_GET_MY_INFO", null, cb); }; messenger.getFriendInfo = function (curvePublic, cb) { postMessage("CONTACTS_GET_FRIEND_INFO", curvePublic, cb); }; messenger.removeFriend = function (curvePublic, cb) { postMessage("CONTACTS_REMOVE_FRIEND", curvePublic, cb); }; messenger.openFriendChannel = function (curvePublic, cb) { postMessage("CONTACTS_OPEN_FRIEND_CHANNEL", curvePublic, cb); }; messenger.getFriendStatus = function (curvePublic, cb) { postMessage("CONTACTS_GET_FRIEND_STATUS", curvePublic, cb); }; messenger.getMoreHistory = function (data, cb) { postMessage("CONTACTS_GET_MORE_HISTORY", data, cb); }; messenger.sendMessage = function (data, cb) { postMessage("CONTACTS_SEND_MESSAGE", data, cb); }; messenger.setChannelHead = function (data, cb) { postMessage("CONTACTS_SET_CHANNEL_HEAD", data, cb); }; messenger.onMessageEvent = Util.mkEvent(); messenger.onJoinEvent = Util.mkEvent(); messenger.onLeaveEvent = Util.mkEvent(); messenger.onUpdateEvent = Util.mkEvent(); messenger.onFriendEvent = Util.mkEvent(); messenger.onUnfriendEvent = Util.mkEvent(); // Pad RPC var pad = common.padRpc = {}; pad.joinPad = function (data) { postMessage("JOIN_PAD", data); }; pad.leavePad = function (data, cb) { postMessage("LEAVE_PAD", data, cb); }; pad.sendPadMsg = function (data, cb) { postMessage("SEND_PAD_MSG", data, cb); }; pad.onReadyEvent = Util.mkEvent(); pad.onMessageEvent = Util.mkEvent(); pad.onJoinEvent = Util.mkEvent(); pad.onLeaveEvent = Util.mkEvent(); pad.onDisconnectEvent = Util.mkEvent(); pad.onConnectEvent = Util.mkEvent(); pad.onErrorEvent = Util.mkEvent(); common.changePadPassword = function (Crypt, href, newPassword, edPublic, cb) { if (!href) { return void cb({ error: 'EINVAL_HREF' }); } var parsed = Hash.parsePadUrl(href); if (!parsed.hash) { return void cb({ error: 'EINVAL_HREF' }); } var warning = false; var newHash; var oldChannel; if (parsed.hashData.password) { newHash = parsed.hash; } else { newHash = Hash.createRandomHash(parsed.type, newPassword); } var newHref = '/' + parsed.type + '/#' + newHash; var optsGet = {}; var optsPut = { password: newPassword }; Nthen(function (waitFor) { if (parsed.hashData && parsed.hashData.password) { common.getPadAttribute('password', waitFor(function (err, password) { optsGet.password = password; }), href); } common.getPadAttribute('owners', waitFor(function (err, owners) { if (!Array.isArray(owners) || owners.indexOf(edPublic) === -1) { // We're not an owner, we shouldn't be able to change the password! waitFor.abort(); return void cb({ error: 'EPERM' }); } optsPut.owners = owners; }), href); common.getPadAttribute('expire', waitFor(function (err, expire) { optsPut.expire = (expire - (+new Date())) / 1000; // Lifetime in seconds }), href); }).nThen(function (waitFor) { Crypt.get(parsed.hash, waitFor(function (err, val) { if (err) { waitFor.abort(); return void cb({ error: err }); } Crypt.put(newHash, val, waitFor(function (err) { if (err) { waitFor.abort(); return void cb({ error: err }); } }), optsPut); }), optsGet); }).nThen(function (waitFor) { var secret = Hash.getSecrets(parsed.type, parsed.hash, optsGet.password); oldChannel = secret.channel; pad.leavePad({ channel: oldChannel }, waitFor()); pad.onDisconnectEvent.fire(true); }).nThen(function (waitFor) { common.removeOwnedChannel(oldChannel, waitFor(function (obj) { if (obj && obj.error) { waitFor.abort(); return void cb(obj); } })); }).nThen(function (waitFor) { common.setPadAttribute('password', newPassword, waitFor(function (err) { if (err) { warning = true; } }), href); var secret = Hash.getSecrets(parsed.type, newHash, newPassword); common.setPadAttribute('channel', secret.channel, waitFor(function (err) { if (err) { warning = true; } }), href); if (parsed.hashData.password) { return; } // same hash common.setPadAttribute('href', newHref, waitFor(function (err) { if (err) { warning = true; } }), href); }).nThen(function () { cb({ warning: warning, hash: newHash, href: newHref }); }); }; common.changeUserPassword = function (Crypt, edPublic, data, cb) { if (!edPublic) { return void cb({ error: 'E_NOT_LOGGED_IN' }); } var accountName = LocalStore.getAccountName(); var hash = LocalStore.getUserHash(); if (!hash) { return void cb({ error: 'E_NOT_LOGGED_IN' }); } var password = data.password; // To remove your old block var newPassword = data.newPassword; // To create your new block var secret = Hash.getSecrets('drive', hash); var newHash, newHref, newSecret, newBlockSeed; var oldIsOwned = false; var blockHash = LocalStore.getBlockHash(); var oldBlockKeys; var Cred, Block, Login; Nthen(function (waitFor) { console.log("loading necessary modules"); require([ '/customize/credential.js', '/common/outer/login-block.js', '/customize/login.js' ], waitFor(function (_Cred, _Block, _Login) { Cred = _Cred; Block = _Block; Login = _Login; })); }).nThen(function (waitFor) { // confirm that the provided password is correct console.log("checking whether the password you entered was correct"); Cred.deriveFromPassphrase(accountName, password, Login.requiredBytes, waitFor(function (bytes) { var allocated = Login.allocateBytes(bytes); oldBlockKeys = allocated.blockKeys; if (blockHash) { if (blockHash !== allocated.blockHash) { console.log("password did not yield the correct blockHash ?"); // incorrect password probably waitFor.abort(); return void cb({ error: 'INVALID_PASSWORD', }); } // the user has already created a block, so you should compare against that } else { // otherwise they're a legacy user, and we should check against the User_hash if (hash !== allocated.userHash) { console.log("password did not yield the correct userHash"); waitFor.abort(); return void cb({ error: 'INVALID_PASSWORD', }); } } })); }).nThen(function (waitFor) { // Check if our drive is already owned console.log("checking if old drive is owned"); common.anonRpcMsg('GET_METADATA', secret.channel, waitFor(function (err, obj) { if (err || obj.error) { return; } if (obj.owners && Array.isArray(obj.owners) && obj.owners.indexOf(edPublic) !== -1) { oldIsOwned = true; } })); }).nThen(function (waitFor) { // Create a new user hash // Get the current content, store it in the new user file // and make sure the new user drive is owned newHash = Hash.createRandomHash('drive'); newHref = '/drive/#' + newHash; newSecret = Hash.getSecrets('drive', newHash); var optsPut = { owners: [edPublic], initialState: '{}', }; console.log("copying contents of old drive to new location"); Crypt.get(hash, waitFor(function (err, val) { if (err) { waitFor.abort(); return void cb({ error: err }); } console.log(val); console.log("%s => %s", hash, newHash); Crypt.put(newHash, val, waitFor(function (err) { if (err) { waitFor.abort(); console.error(err); return void cb({ error: err }); } console.error('checking content at newHash: %s', newHash); Crypt.get(newHash, function (err, val) { if (err) { waitFor.abort(); console.log(err); return void cb({ error: err }); } console.error(val); }); }), optsPut); })); }).nThen(function (waitFor) { // Drive content copied: get the new block location console.log("deriving new credentials from passphrase"); Cred.deriveFromPassphrase(accountName, newPassword, Login.requiredBytes, waitFor(function (bytes) { var allocated = Login.allocateBytes(bytes); newBlockSeed = allocated.blockSeed; })); }).nThen(function (waitFor) { // Write the new login block var keys = Block.genkeys(newBlockSeed); var temp = { User_name: accountName, User_hash: newHash, edPublic: edPublic, // edPrivate XXX }; var content = Block.serialize(JSON.stringify(temp), keys); console.log("writing new login block"); common.writeLoginBlock(content, waitFor(function (obj) { console.log("new login block written"); var newBlockHash = Block.getBlockHash(keys); LocalStore.setBlockHash(newBlockHash); if (obj && obj.error) { waitFor.abort(); return void cb(obj); } })); }).nThen(function (waitFor) { // New drive hash is in login block, unpin the old one and pin the new one console.log("unpinning old drive and pinning new one"); common.unpinPads([secret.channel], waitFor()); common.pinPads([newSecret.channel], waitFor()); }).nThen(function (waitFor) { // Remove block hash if (blockHash) { console.log('removing old login block'); var removeData = Block.remove(oldBlockKeys); common.removeLoginBlock(removeData, waitFor(function (obj) { if (obj && obj.error) { return void console.error(obj.error); } })); } }).nThen(function (waitFor) { if (oldIsOwned) { console.log('removing old drive'); common.removeOwnedChannel(secret.channel, waitFor(function (obj) { if (obj && obj.error) { // Deal with it as if it was not owned oldIsOwned = false; return; } common.logoutFromAll(waitFor(function () { postMessage("DISCONNECT"); })); })); } }).nThen(function (waitFor) { if (!oldIsOwned) { console.error('deprecating old drive.'); postMessage("SET", { key: [Constants.deprecatedKey], value: true }, waitFor(function (obj) { if (obj && obj.error) { console.error(obj.error); } common.logoutFromAll(waitFor(function () { postMessage("DISCONNECT"); })); })); } }).nThen(function () { // We have the new drive, with the new login block window.location.reload(); }); }; // Loading events common.loading = {}; common.loading.onDriveEvent = Util.mkEvent(); common.getFullHistory = function (data, cb) { postMessage("GET_FULL_HISTORY", data, cb); }; common.getHistoryRange = function (data, cb) { postMessage("GET_HISTORY_RANGE", data, cb); }; common.getShareHashes = function (secret, cb) { var hashes; if (!window.location.hash) { hashes = Hash.getHashes(secret); return void cb(null, hashes); } var parsed = Hash.parsePadUrl(window.location.href); if (!parsed.type || !parsed.hashData) { return void cb('E_INVALID_HREF'); } hashes = Hash.getHashes(secret); if (secret.version === 0) { // It means we're using an old hash hashes.editHash = window.location.hash.slice(1); return void cb(null, hashes); } if (hashes.editHash) { // no need to find stronger if we already have edit hash return void cb(null, hashes); } postMessage("GET_STRONGER_HASH", { href: window.location.href, channel: secret.channel, password: secret.password }, function (hash) { if (hash) { hashes.editHash = hash; } cb(null, hashes); }); }; var CRYPTPAD_VERSION = 'cryptpad-version'; var currentVersion = localStorage[CRYPTPAD_VERSION]; var updateLocalVersion = function (newUrlArgs) { // Check for CryptPad updates var urlArgs = newUrlArgs || (Config.requireConf ? Config.requireConf.urlArgs : null); if (!urlArgs) { return; } var arr = /ver=([0-9.]+)(-[0-9]*)?/.exec(urlArgs); var ver = arr[1]; if (!ver) { return; } var verArr = ver.split('.'); verArr[2] = 0; if (verArr.length !== 3) { return; } var stored = currentVersion || '0.0.0'; var storedArr = stored.split('.'); storedArr[2] = 0; var shouldUpdate = parseInt(verArr[0]) > parseInt(storedArr[0]) || (parseInt(verArr[0]) === parseInt(storedArr[0]) && parseInt(verArr[1]) > parseInt(storedArr[1])); if (!shouldUpdate) { return; } currentVersion = ver; localStorage[CRYPTPAD_VERSION] = ver; if (newUrlArgs) { // It's a reconnect common.onNewVersionReconnect.fire(); } return true; }; 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/'; }; common.startAccountDeletion = function (data, cb) { // Logout other tabs LocalStore.logout(null, true); cb(); }; var queries = { REQUEST_LOGIN: requestLogin, UPDATE_METADATA: common.changeMetadata, UPDATE_TOKEN: function (data) { var localToken = tryParsing(localStorage.getItem(Constants.tokenKey)); if (localToken !== data.token) { requestLogin(); } }, // Messaging Q_FRIEND_REQUEST: common.messaging.onFriendRequest.fire, EV_FRIEND_COMPLETE: common.messaging.onFriendComplete.fire, // Network NETWORK_DISCONNECT: common.onNetworkDisconnect.fire, NETWORK_RECONNECT: function (data) { require(['/api/config?' + (+new Date())], function (NewConfig) { var update = updateLocalVersion(NewConfig.requireConf && NewConfig.requireConf.urlArgs); if (update) { postMessage('DISCONNECT'); return; } common.onNetworkReconnect.fire(data); }); }, // Messenger CONTACTS_MESSAGE: common.messenger.onMessageEvent.fire, CONTACTS_JOIN: common.messenger.onJoinEvent.fire, CONTACTS_LEAVE: common.messenger.onLeaveEvent.fire, CONTACTS_UPDATE: common.messenger.onUpdateEvent.fire, CONTACTS_FRIEND: common.messenger.onFriendEvent.fire, CONTACTS_UNFRIEND: common.messenger.onUnfriendEvent.fire, // Pad PAD_READY: common.padRpc.onReadyEvent.fire, PAD_MESSAGE: common.padRpc.onMessageEvent.fire, PAD_JOIN: common.padRpc.onJoinEvent.fire, PAD_LEAVE: common.padRpc.onLeaveEvent.fire, PAD_DISCONNECT: common.padRpc.onDisconnectEvent.fire, PAD_CONNECT: common.padRpc.onConnectEvent.fire, PAD_ERROR: common.padRpc.onErrorEvent.fire, // Drive DRIVE_LOG: common.drive.onLog.fire, DRIVE_CHANGE: common.drive.onChange.fire, DRIVE_REMOVE: common.drive.onRemove.fire, // Account deletion DELETE_ACCOUNT: common.startAccountDeletion, // Loading LOADING_DRIVE: common.loading.onDriveEvent.fire }; common.hasCSSVariables = function () { if (window.CSS && window.CSS.supports && window.CSS.supports('--a', 0)) { return true; } // Safari lol y u always b returnin false ? var color = 'rgb(255, 198, 0)'; var el = document.createElement('span'); el.style.setProperty('--color', color); el.style.setProperty('background', 'var(--color)'); document.body.appendChild(el); var styles = getComputedStyle(el); var doesSupport = (styles.backgroundColor === color); document.body.removeChild(el); return doesSupport; }; common.ready = (function () { var env = {}; var initialized = false; return function (f, rdyCfg) { rdyCfg = rdyCfg || {}; if (initialized) { return void setTimeout(function () { f(void 0, env); }); } var provideFeedback = function () { if (typeof(window.Proxy) === 'undefined') { Feedback.send("NO_PROXIES"); } var shimPattern = /CRYPTPAD_SHIM/; if (shimPattern.test(Array.isArray.toString())) { Feedback.send("NO_ISARRAY"); } if (shimPattern.test(Array.prototype.fill.toString())) { Feedback.send("NO_ARRAYFILL"); } if (typeof(Symbol) === 'undefined') { Feedback.send('NO_SYMBOL'); } if (typeof(SharedWorker) === "undefined") { Feedback.send('NO_SHAREDWORKER'); } else { Feedback.send('SHAREDWORKER'); } if (typeof(Worker) === "undefined") { Feedback.send('NO_WEBWORKER'); } if (!('serviceWorker' in navigator)) { Feedback.send('NO_SERVICEWORKER'); } if (!common.hasCSSVariables()) { Feedback.send('NO_CSS_VARIABLES'); } Feedback.reportScreenDimensions(); Feedback.reportLanguage(); }; var initFeedback = function (feedback) { // Initialize feedback Feedback.init(feedback); provideFeedback(); }; var userHash; Nthen(function (waitFor) { if (AppConfig.beforeLogin) { AppConfig.beforeLogin(LocalStore.isLoggedIn(), waitFor()); } }).nThen(function (waitFor) { var blockHash = LocalStore.getBlockHash(); if (blockHash) { console.log(blockHash); var parsed = Block.parseBlockHash(blockHash); if (typeof(parsed) !== 'object') { console.error("Failed to parse blockHash"); console.log(parsed); return; } else { console.log(parsed); } Util.fetch(parsed.href, waitFor(function (err, arraybuffer) { if (err) { return void console.log(err); } // use the results to load your user hash and // put your userhash into localStorage try { var block_info = Block.decrypt(arraybuffer, parsed.keys); if (!block_info) { console.error("Failed to decrypt !"); return; } userHash = block_info[Constants.userHashKey]; if (!userHash || userHash !== LocalStore.getUserHash()) { return void requestLogin(); } } catch (e) { console.error(e); return void console.error("failed to decrypt or decode block content"); } })); } }).nThen(function (waitFor) { var cfg = { init: true, userHash: userHash || LocalStore.getUserHash(), anonHash: LocalStore.getFSHash(), localToken: tryParsing(localStorage.getItem(Constants.tokenKey)), // TODO move tihs to LocalStore ? language: common.getLanguage(), messenger: rdyCfg.messenger, // Boolean driveEvents: rdyCfg.driveEvents // Boolean }; if (sessionStorage[Constants.newPadPathKey]) { common.initialPath = sessionStorage[Constants.newPadPathKey]; delete sessionStorage[Constants.newPadPathKey]; } var channelIsReady = waitFor(); var msgEv = Util.mkEvent(); var postMsg, worker; var noWorker = AppConfig.disableWorkers || false; var noSharedWorker = false; if (localStorage.CryptPad_noWorkers) { noWorker = localStorage.CryptPad_noWorkers === '1'; console.error('WebWorker/SharedWorker state forced to ' + !noWorker); } Nthen(function (waitFor2) { if (Worker) { var w = waitFor2(); worker = new Worker('/common/outer/testworker.js?' + urlArgs); worker.onerror = function (errEv) { errEv.preventDefault(); errEv.stopPropagation(); noWorker = true; w(); }; worker.onmessage = function (ev) { if (ev.data === "OK") { w(); } }; } if (typeof(SharedWorker) !== "undefined") { try { new SharedWorker(''); } catch (e) { noSharedWorker = true; console.log('Disabling SharedWorker because of privacy settings.'); } } }).nThen(function (waitFor2) { if (!noWorker && !noSharedWorker && typeof(SharedWorker) !== "undefined") { worker = new SharedWorker('/common/outer/sharedworker.js?' + urlArgs); worker.onerror = function (e) { console.error(e.message); }; worker.port.onmessage = function (ev) { if (ev.data === "SW_READY") { return; } msgEv.fire(ev); }; postMsg = function (data) { worker.port.postMessage(data); }; postMsg('INIT'); window.addEventListener('beforeunload', function () { postMsg('CLOSE'); }); } else if (false && !noWorker && !noSharedWorker && 'serviceWorker' in navigator) { var initializing = true; var stopWaiting = waitFor2(); // Call this function when we're ready postMsg = function (data) { if (worker) { return void worker.postMessage(data); } }; navigator.serviceWorker.register('/common/outer/serviceworker.js?' + urlArgs, {scope: '/'}) .then(function(reg) { // Add handler for receiving messages from the service worker navigator.serviceWorker.addEventListener('message', function (ev) { if (initializing && ev.data === "SW_READY") { initializing = false; } else { msgEv.fire(ev); } }); // Initialize the worker // If it is active (probably running in another tab), just post INIT if (reg.active) { worker = reg.active; postMsg("INIT"); } // If it was not active, wait for the "activated" state and post INIT reg.onupdatefound = function () { if (initializing) { var w = reg.installing; var onStateChange = function () { if (w.state === "activated") { worker = w; postMsg("INIT"); w.removeEventListener("statechange", onStateChange); } }; w.addEventListener('statechange', onStateChange); return; } // New version detected (from another tab): kill? console.error('New version detected: ABORT?'); }; return void stopWaiting(); }).catch(function(error) { /**/console.log('Registration failed with ' + error); }); window.addEventListener('beforeunload', function () { postMsg('CLOSE'); }); } else if (!noWorker && Worker) { worker = new Worker('/common/outer/webworker.js?' + urlArgs); worker.onerror = function (e) { console.error(e.message); }; worker.onmessage = function (ev) { msgEv.fire(ev); }; postMsg = function (data) { worker.postMessage(data); }; } else { // Use the async store in the main thread if workers are not available require(['/common/outer/noworker.js'], waitFor2(function (NoWorker) { NoWorker.onMessage(function (data) { msgEv.fire({data: data}); }); postMsg = function (d) { setTimeout(function () { NoWorker.query(d); }); }; NoWorker.create(); })); } }).nThen(function () { Channel.create(msgEv, postMsg, function (chan) { console.log('Outer ready'); Object.keys(queries).forEach(function (q) { chan.on(q, function (data, cb) { try { queries[q](data, cb); } catch (e) { console.error("Error in outer when executing query " + q); console.error(e); console.log(data); } }); }); postMessage = function (cmd, data, cb) { cb = cb || function () {}; chan.query(cmd, data, function (err, data) { if (err) { return void cb ({error: err}); } cb(data); }); }; console.log('Posting CONNECT'); postMessage('CONNECT', cfg, function (data) { if (data.error) { throw new Error(data.error); } if (data.state === 'ALREADY_INIT') { data = data.returned; } if (data.anonHash && !cfg.userHash) { LocalStore.setFSHash(data.anonHash); } /*if (cfg.userHash && sessionStorage) { // copy User_hash into sessionStorage because cross-domain iframes // on safari replaces localStorage with sessionStorage or something sessionStorage.setItem(Constants.userHashKey, cfg.userHash); }*/ if (cfg.userHash) { var localToken = tryParsing(localStorage.getItem(Constants.tokenKey)); if (localToken === null) { // if that number hasn't been set to localStorage, do so. localStorage.setItem(Constants.tokenKey, data[Constants.tokenKey]); } } initFeedback(data.feedback); initialized = true; channelIsReady(); }); }, false); }); }).nThen(function (waitFor) { // Load the new pad when the hash has changed var oldHref = document.location.href; window.onhashchange = function (ev) { if (ev && ev.reset) { oldHref = document.location.href; return; } var newHref = document.location.href; // Compare the URLs without /embed and /present var parsedOld = Hash.parsePadUrl(oldHref); var parsedNew = Hash.parsePadUrl(newHref); if (parsedOld.hashData && parsedNew.hashData && parsedOld.getUrl() !== parsedNew.getUrl()) { if (!parsedOld.hashData.key) { oldHref = newHref; return; } // If different, reload document.location.reload(); return; } if (parsedNew.hashData) { oldHref = newHref; } }; // Listen for login/logout in other tabs window.addEventListener('storage', function (e) { if (e.key !== Constants.userHashKey) { return; } var o = e.oldValue; var n = e.newValue; if (!o && n) { document.location.reload(); } else if (o && !n) { LocalStore.logout(); } }); LocalStore.onLogout(function () { console.log('onLogout: disconnect'); postMessage("DISCONNECT"); }); if (PINNING_ENABLED && LocalStore.isLoggedIn()) { console.log("logged in. pads will be pinned"); postMessage("INIT_RPC", null, waitFor(function (obj) { console.log('RPC handshake complete'); if (obj.error) { return; } localStorage.plan = obj.plan; })); } else if (PINNING_ENABLED) { console.log('not logged in. pads will not be pinned'); } else { console.log('pinning disabled'); } postMessage("INIT_ANON_RPC", null, waitFor(function () { console.log('Anonymous RPC ready'); })); }).nThen(function (waitFor) { if (sessionStorage.createReadme) { 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) { common.mergeAnonDrive(waitFor(function() { delete sessionStorage.migrateAnonDrive; })); } }).nThen(function (waitFor) { if (AppConfig.afterLogin) { AppConfig.afterLogin(common, waitFor()); } }).nThen(function () { updateLocalVersion(); f(void 0, env); if (typeof(window.onhashchange) === 'function') { window.onhashchange(); } }); }; }()); return common; });