define([ 'jquery', '/api/config', '/common/cryptget.js', '/common/pinpad.js', '/common/common-constants.js', '/common/common-hash.js', '/common/outer/local-store.js', '/common/outer/login-block.js', '/common/outer/network-config.js', '/customize/login.js', '/common/test.js', '/bower_components/nthen/index.js', '/bower_components/netflux-websocket/netflux-client.js', '/bower_components/tweetnacl/nacl-fast.min.js' ], function ($, ApiConfig, Crypt, Pinpad, Constants, Hash, LocalStore, Block, NetConfig, Login, Test, nThen, Netflux) { var Nacl = window.nacl; var signMsg = function (msg, privKey) { var signKey = Nacl.util.decodeBase64(privKey); var buffer = Nacl.util.decodeUTF8(msg); return Nacl.util.encodeBase64(Nacl.sign(buffer, signKey)); }; // TODO: Allow authing for any domain as long as the user clicks an "accept" button // inside of the iframe. var AUTHORIZED_DOMAINS = [ /\.cryptpad\.fr$/, /^http(s)?:\/\/localhost\:/ ]; // Safari is weird about localStorage in iframes but seems to let sessionStorage slide. localStorage[Constants.userHashKey] = localStorage[Constants.userHashKey] || sessionStorage[Constants.userHashKey]; var proxy; var rpc; var network; var rpcError; var contacts = {}; var loadProxy = function (hash) { nThen(function (waitFor) { var wsUrl = NetConfig.getWebsocketURL(); var w = waitFor(); Netflux.connect(wsUrl).then(function (_network) { network = _network; w(); }, function (err) { rpcError = err; console.error(err); }); }).nThen(function (waitFor) { Crypt.get(hash, waitFor(function (err, val) { if (err) { waitFor.abort(); console.error(err); return; } try { var parsed = JSON.parse(val); proxy = parsed; } catch (e) { console.log("Can't parse user drive", e); } }), { network: network }); }).nThen(function () { var origin = ApiConfig.fileHost || window.location.origin; // Get contacts and extract their avatar channel and key var getData = function (obj, href) { var parsed = Hash.parsePadUrl(href); if (!parsed || parsed.type !== "file") { return; } // XXX var secret = Hash.getSecrets('file', parsed.hash); if (!secret.keys || !secret.channel) { return; } obj.avatarKey = Hash.encodeBase64(secret.keys && secret.keys.cryptKey); obj.avatarSrc = origin + Hash.getBlobPathFromHex(secret.channel); }; contacts.teams = proxy.teams || {}; contacts.friends = proxy.friends || {}; Object.keys(contacts.friends).map(function (key) { var friend = contacts.friends[key]; // if (!friend) { return; } // XXX how should this be handled? var ret = { edPublic: friend.edPublic, name: friend.displayName, }; getData(ret, friend.avatar); contacts.friends[key] = ret; }); Object.keys(contacts.teams).map(function (key) { var team = contacts.teams[key]; // if (!team) { return; } // XXX how should this be handled. Is this possible? var avatar = team.metadata && team.metadata.avatar; var ret = { edPublic: team.keys && team.keys.drive && team.keys.drive.edPublic, name: team.metadata && team.metadata.name }; getData(ret, avatar); contacts.teams[key] = ret; }); contacts.origin = window.location.origin; }).nThen(function (waitFor) { if (!network) { return void waitFor.abort(); } Pinpad.create(network, proxy, waitFor(function (e, call) { if (e) { rpcError = e; return void waitFor.abort(); } rpc = call; })); }).nThen(function () { Test(function () { // This is only here to maybe trigger an error. window.drive = proxy['drive']; Test.passed(); }); }); }; var whenReady = function (cb) { if (proxy && (rpc || rpcError)) { return void cb(); } console.log('CryptPad not ready...'); setTimeout(function () { whenReady(cb); }, 100); }; $(window).on("message", function (jqe) { var evt = jqe.originalEvent; var data = JSON.parse(evt.data); var domain = evt.origin; var srcWindow = evt.source; var ret = { txid: data.txid }; console.log('CP receiving', data); if (data.cmd === 'PING') { ret.res = 'PONG'; } else if (data.cmd === 'LOGIN') { Login.loginOrRegister(data.data.name, data.data.password, false, false, function (err) { if (err) { ret.error = 'LOGIN_ERROR'; srcWindow.postMessage(JSON.stringify(ret), domain); return; } loadProxy(LocalStore.getUserHash()); srcWindow.postMessage(JSON.stringify(ret), domain); }); return; } else if (data.cmd === 'SIGN') { if (!AUTHORIZED_DOMAINS.filter(function (x) { return x.test(domain); }).length) { ret.error = "UNAUTH_DOMAIN"; } else if (!LocalStore.isLoggedIn()) { ret.error = "NOT_LOGGED_IN"; } else { return void whenReady(function () { var sig = signMsg(data.data, proxy.edPrivate); ret.res = { uname: proxy.login_name, edPublic: proxy.edPublic, sig: sig }; ret.contacts = contacts; srcWindow.postMessage(JSON.stringify(ret), domain); }); } } else if (data.cmd === 'UPDATE_LIMIT') { return void whenReady(function () { if (rpcError) { // Tell the user on accounts that there was an issue and they need to wait maximum 24h or contact an admin ret.warning = true; srcWindow.postMessage(JSON.stringify(ret), domain); return; } rpc.updatePinLimits(function (e, limit, plan, note) { if (e) { ret.warning = true; } ret.res = [limit, plan, note]; srcWindow.postMessage(JSON.stringify(ret), domain); }); }); } else { ret.error = "UNKNOWN_CMD"; } srcWindow.postMessage(JSON.stringify(ret), domain); }); var userHash = LocalStore.getUserHash(); if (userHash) { loadProxy(userHash); } });