diff --git a/customize.dist/index.html b/customize.dist/index.html index a49dbba1e..2e2eb28bb 100644 --- a/customize.dist/index.html +++ b/customize.dist/index.html @@ -72,9 +72,7 @@
Be careful not to forget your credentials, as they are impossible to recover
'; + return out; }); diff --git a/customize.dist/share/frame.js b/customize.dist/share/frame.js index 0e32593c8..86147ca4b 100644 --- a/customize.dist/share/frame.js +++ b/customize.dist/share/frame.js @@ -49,6 +49,15 @@ }); }; + var changeHandlers = frame.changeHandlers = []; + + var change = frame.change = function (f) { + if (typeof(f) !== 'function') { + throw new Error('[Frame.change] expected callback'); + } + changeHandlers.push(f); + }; + var _listener = function (e) { if (!frame.accepts(e.origin)) { console.log("message from %s rejected!", e.origin); @@ -63,6 +72,14 @@ console.log("No uid!"); return; } + + if (uid === 'change' && changeHandlers.length) { + changeHandlers.forEach(function (f) { + f(data); + }); + return; + } + if (timeouts[uid]) { window.clearTimeout(timeouts[uid]); } @@ -89,6 +106,11 @@ }; var id = req._uid = uid(); + // uid must not equal 'change' + while(id === 'change') { + id = req._uid = uid(); + } + if (typeof(cb) === 'function') { //console.log("setting callback!"); listeners[id] = cb; diff --git a/customize.dist/share/respond.js b/customize.dist/share/respond.js index b5ae4671e..7001ed16e 100644 --- a/customize.dist/share/respond.js +++ b/customize.dist/share/respond.js @@ -81,3 +81,13 @@ window.addEventListener('message', function(e) { } }); +window.addEventListener('storage', function (ev) { + parent.postMessage(JSON.stringify({ + _uid: 'change', + data: { + key: ev.key, + oldValue: ev.oldValue, + newValue: ev.newValue, + } + }), '*'); +}); diff --git a/customize.dist/src/cryptpad.less b/customize.dist/src/cryptpad.less index 3304c312c..c70bb3168 100644 --- a/customize.dist/src/cryptpad.less +++ b/customize.dist/src/cryptpad.less @@ -471,7 +471,63 @@ form.realtime { #addoption { .bottom-left; } } -div.modal { +.viewportRatio (@x, @y, @p: 100) { + width: @p * 100vw; + height: @y * (@p * 100vw) / @x; + max-width: @x / @y * (@p * 100vh); + max-height: (@p * 100vh); +} + +div.modal, div#modal { + + #content { + box-sizing: border-box; + .size (@n) { + font-size: @n * 1vw; + line-height: @n * 1.2vw; + } + + border: 1px solid white; + + vertical-align: middle; + padding: 2.5vw; + + + width: 100vw; + height: 56.25vw; // height:width ratio = 9/16 = .5625 + //background: pink; + max-height: 100vh; + max-width: 177.78vh; // 16/9 = 1.778 + margin: auto; + position: absolute; + top:0;bottom:0; // vertical center + left:0;right:0; // horizontal center + + p, li, pre, code { + .size(2.5); + } + + h1 { .size(5); } + h2 { .size(4.2); } + h3 { .size(3.6); } + h4 { .size (3); } + h5 { .size(2.2); } + h6 { .size(1.6); } + + + pre > code { + + display: block; + position: relative; + border: 1px solid #333; + width: 90%; + margin: auto; + padding-left: .25vw; + + } + + } + box-sizing: border-box; z-index: 9001; position: fixed; diff --git a/customize.dist/store.js b/customize.dist/store.js index 287b95644..5b82921ed 100644 --- a/customize.dist/store.js +++ b/customize.dist/store.js @@ -71,5 +71,27 @@ define(function () { } }; + var changeHandlers = Store.changeHandlers = []; + + Store.change = function (f) { + if (typeof(f) !== 'function') { + throw new Error('[Store.change] callback must be a function'); + } + changeHandlers.push(f); + + if (changeHandlers.length === 1) { + // start listening for changes + window.addEventListener('storage', function (e) { + changeHandlers.forEach(function (f) { + f({ + key: e.key, + oldValue: e.oldValue, + newValue: e.newValue, + }); + }); + }); + } + }; + return Store; }); diff --git a/customize.dist/user.js b/customize.dist/user.js new file mode 100644 index 000000000..c75a83653 --- /dev/null +++ b/customize.dist/user.js @@ -0,0 +1,174 @@ +define([ + '/api/config?cb=' + Math.random().toString().slice(2), + '/customize/messages.js', + '/bower_components/chainpad-listmap/chainpad-listmap.js', + '/bower_components/chainpad-crypto/crypto.js', + '/bower_components/scrypt-async/scrypt-async.min.js', + '/bower_components/tweetnacl/nacl-fast.min.js', +], function (Config, Messages, Listmap, Crypto) { + var Scrypt = window.scrypt; + var Nacl = window.nacl; + + var localKey = 'cryptpad_user_session'; + + var User = {}; + + var isArray = function (o) { return Object.prototype.toString.call(o) === '[object Array]'; }; + + var session = User.session = function (secret) { + // TODO use store.js, not localStorage? + if (secret) { + localStorage.setItem(localKey, JSON.stringify(secret)); + return; + } + if (secret === null) { + localStorage.removeItem(localKey); + return; + } + var temp = localStorage.getItem(localKey); + try { + return JSON.parse(temp); + } catch (err) { + return null; + } + }; + + /* 64 uint8s symmetric keys + 32 b64 channel + 16 b64 key + 16 b64 junk + 32 uint8s ed signing key + 32 uint8s curve public key */ + var parse128 = function (A) { + if (A.length !== 128) { + throw new Error("Expected 128 uint8s!"); + } + var symmetric = Nacl.util.encodeBase64(A.slice(0, 36)); + return { + ed: A.slice(96), + curve: A.slice(64, 96), + channel: symmetric.slice(0, 32), + key: symmetric.slice(32), + extra: A.slice(36, 64), + }; + }; + + var initialize = User.initialize = function (proxy, secret, cb) { + proxy.on('ready', function (info) { + var now = ''+new Date(); + // old atime + var otime = proxy.atime; + + var atime = proxy.atime = now; + + // creation time + proxy.ctime = proxy.ctime || now; + + proxy.username = proxy.username || secret.username; + proxy.schema = proxy.schema || 'login_data-v0'; + + proxy.documents = proxy.documents || []; + cb(void 0, proxy); + }); + }; + + /* + cb(proxy); + */ + var connect = User.connect = function (secret, cb) { + if (!secret) { + // FIXME + return; + } + var config = { + websocketURL: Config.websocketURL, + channel: secret.channel, + data: {}, + crypto: Crypto.createEncryptor(secret.key), + logLevel: 0, + }; + var rt = Listmap.create(config); + initialize(rt.proxy, secret, cb); + }; + + /* Asynchronously derive 128 random uint8s given a uname and password + + cb(proxy, secret) + */ + var login = User.login = function (uname, pw, cb) { + Scrypt(pw, + uname, + 15, // memory cost parameter + 8, // block size parameter + 128, // derived key length + 200, // interruptStep + function (bytes) { + var secret = parse128(bytes); + secret.username = uname; + session(secret); + connect(secret, cb); + }); + }; + + var prepareStore = User.prepareStore = function (proxy) { + var store = {}; + + var ps = proxy.store = proxy.store || {}; + + var set = store.set = function (key, val, cb) { + ps[key] = val; + cb(); + }; + + var batchset = store.setBatch = function (map, cb) { + if (isArray(map) || typeof(map) !== 'object') { + cb('[setBatch.TypeError] expected key-value pairs to set'); + return; + } + Object.keys(map).forEach(function (k) { + ps[k] = map[k]; + }); + cb(); + }; + + var get = store.get = function (key, cb) { + cb(void 0, ps[key]); + }; + + var batchget = store.getBatch = function (keys, cb) { + if (!isArray(keys)) { + cb('[getBatch.TypeError] expected array of keys to return'); + return; + } + var map = {}; + keys.forEach(function (k) { + map[k] = ps[k]; + }); + cb(void 0, map); + }; + + var remove = store.remove = function (key, cb) { + ps[key] = undefined; + cb(); + }; + + var batchremove = store.removeBatch = function (keys, cb) { + if (!isArray(keys)) { + cb('[batchremove.TypeError] expected array of keys to remove'); + return; + } + keys.forEach(function (k) { + ps[k] = undefined; + }); + cb(); + }; + + var keys = store.keys = function (cb) { + cb(void 0, Object.keys(ps)); + }; + + return store; + }; + + return User; +}); diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index d6e962d9c..a4b949629 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -4,8 +4,11 @@ define([ '/bower_components/chainpad-crypto/crypto.js', '/bower_components/alertifyjs/dist/js/alertify.js', '/bower_components/spin.js/spin.min.js', + + '/customize/user.js', + '/bower_components/jquery/dist/jquery.min.js', -], function (Messages, Store, Crypto, Alertify, Spinner) { +], function (Messages, Store, Crypto, Alertify, Spinner, User) { /* 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. @@ -14,13 +17,32 @@ define([ */ var $ = window.jQuery; + var common = {}; var store; + var userProxy; + var userStore; - var getStore = function () { - if (!store) { - throw new Error("Store is not ready!"); + var getStore = common.getStore = function (legacy) { + if (!legacy && userStore) { return userStore; } + if (store) { return store; } + throw new Error("Store is not ready!"); + }; + + /* + * cb(err, proxy); + */ + var authorize = common.authorize = function (cb) { + console.log("Authorizing"); + var secret = User.session(); + if (!secret) { + // user is not authenticated + cb('user is not authenticated', void 0); } - return store; + + // for now we assume that things always work + User.connect(secret, function (err, proxy) { + cb(void 0, proxy); + }); }; Store.ready(function (err, Store) { @@ -31,7 +53,6 @@ define([ store = Store; }); - var common = {}; var isArray = function (o) { return Object.prototype.toString.call(o) === '[object Array]'; }; @@ -83,6 +104,9 @@ define([ var storageKey = common.storageKey = 'CryptPad_RECENTPADS'; + /* + * 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. @@ -129,41 +153,6 @@ define([ return window.location.hash.slice(1); }; - var setPadAttribute = common.setPadAttribute = function (attr, value, cb) { - getStore().set([getHash(), attr].join('.'), value, function (err, data) { - cb(err, data); - }); - }; - - var getPadAttribute = common.getPadAttribute = function (attr, cb) { - getStore().get([getHash(), attr].join('.'), function (err, data) { - cb(err, data); - }); - }; - - /* fetch and migrate your pad history from localStorage */ - var getRecentPads = common.getRecentPads = function (cb) { - getStore().get(storageKey, function (err, recentPads) { - if (isArray(recentPads)) { - cb(void 0, migrateRecentPads(recentPads)); - return; - } - cb(void 0, []); - }); - }; - - /* commit a list of pads to localStorage */ - var setRecentPads = common.setRecentPads = function (pads, cb) { - getStore().set(storageKey, pads, function (err, data) { - cb(err, data); - }); - }; - - /* 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(); - }; - var parsePadUrl = common.parsePadUrl = function (href) { var patt = /^https*:\/\/([^\/]*)\/(.*?)\/#(.*)$/i; @@ -177,7 +166,57 @@ define([ return ret; }; - var forgetPad = common.forgetPad = function (href, cb) { + 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, legacy) { + getStore(legacy).set([getHash(), attr].join('.'), value, function (err, data) { + cb(err, data); + }); + }; + + // STORAGE + var getPadAttribute = common.getPadAttribute = function (attr, cb, legacy) { + getStore(legacy).get([getHash(), attr].join('.'), function (err, data) { + cb(err, data); + }); + }; + + // STORAGE + /* fetch and migrate your pad history from localStorage */ + var getRecentPads = common.getRecentPads = function (cb, legacy) { + getStore(legacy).get(storageKey, function (err, recentPads) { + if (isArray(recentPads)) { + cb(void 0, migrateRecentPads(recentPads)); + return; + } + cb(void 0, []); + }); + }; + + // STORAGE + /* commit a list of pads to localStorage */ + var setRecentPads = common.setRecentPads = function (pads, cb, legacy) { + getStore(legacy).set(storageKey, pads, function (err, data) { + cb(err, data); + }); + }; + + // STORAGE + var forgetPad = common.forgetPad = function (href, cb, legacy) { var parsed = parsePadUrl(href); getRecentPads(function (err, recentPads) { @@ -195,7 +234,7 @@ define([ return; } - getStore().keys(function (err, keys) { + getStore(legacy).keys(function (err, keys) { if (err) { cb(err); return; @@ -208,24 +247,15 @@ define([ cb(); return; } - getStore().removeBatch(toRemove, function (err, data) { + getStore(legacy).removeBatch(toRemove, function (err, data) { cb(err, data); }); }); - }); - }); - }; - - var makePad = function (href, title) { - var now = ''+new Date(); - return { - href: href, - atime: now, - ctime: now, - title: title || window.location.hash.slice(1, 9), - }; + }, legacy); + }, legacy); }; + // STORAGE var rememberPad = common.rememberPad = window.rememberPad = function (title, cb) { // bail out early if (!/#/.test(window.location.hash)) { return; } @@ -265,6 +295,7 @@ define([ }); }; + // STORAGE var setPadTitle = common.setPadTitle = function (name, cb) { var href = window.location.href; var parsed = parsePadUrl(href); @@ -301,6 +332,7 @@ define([ }); }; + // STORAGE var getPadTitle = common.getPadTitle = function (cb) { var href = window.location.href; var parsed = parsePadUrl(window.location.href); @@ -324,6 +356,7 @@ define([ }); }; + // STORAGE var causesNamingConflict = common.causesNamingConflict = function (title, cb) { var href = window.location.href; @@ -364,13 +397,30 @@ define([ } }; - state++; + state = 2; Store.ready(function (err, store) { - env.store = store; + common.store = env.store = store; + cb(); + }); + + // HERE + authorize(function (err, proxy) { + if (err) { + // not logged in + } + if (!proxy) { + cb(); + return; + } + userProxy = env.proxy = proxy; + userStore = env.userStore = User.prepareStore(proxy); cb(); }); }; + /* + * Saving files + */ var fixFileName = common.fixFileName = function (filename) { return filename.replace(/ /g, '-').replace(/[\/\?]/g, '_') .replace(/_+/g, '_'); @@ -388,6 +438,9 @@ define([ }; }; + /* + * Alertifyjs + */ var styleAlerts = common.styleAlerts = function (href) { var $link = $('link[href="/customize/alertify.css"]'); if ($link.length) { return; } @@ -492,6 +545,9 @@ define([ Alertify.error(msg); }; + /* + * spinner + */ common.spinner = function (parent) { var $target = $('