diff --git a/.jshintrc b/.jshintrc index aeddcda04..c95e9dbc4 100644 --- a/.jshintrc +++ b/.jshintrc @@ -7,7 +7,6 @@ "iterator": true, "latedef": true, "nocomma": true, - "notypeof": true, "shadow": false, "undef": true, "unused": true, diff --git a/bower.json b/bower.json index 10ab90388..fad124ba9 100644 --- a/bower.json +++ b/bower.json @@ -44,7 +44,8 @@ "open-sans-fontface": "^1.4.2", "bootstrap-tokenfield": "^0.12.1", "localforage": "^1.5.2", - "html2canvas": "^0.4.1" + "html2canvas": "^0.4.1", + "croppie": "^2.5.0" }, "resolutions": { "bootstrap": "v4.0.0-alpha.6" diff --git a/config.example.js b/config.example.js index ce38b5c3a..3a097eb87 100644 --- a/config.example.js +++ b/config.example.js @@ -159,6 +159,24 @@ module.exports = { */ defaultStorageLimit: 50 * 1024 * 1024, + /* + * CryptPad allows administrators to give custom limits to their friends. + * add an entry for each friend, identified by their user id, + * which can be found on the settings page. Include a 'limit' (number of bytes), + * a 'plan' (string), and a 'note' (string). + * + * hint: 1GB is 1024 * 1024 * 1024 bytes + */ + customLimits: { + /* + "https://my.awesome.website/user/#/1/cryptpad-user/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=": { + limit: 20 * 1024 * 1024 * 1024, + plan: 'insider', + note: 'storage space donated by my.awesome.website' + } + */ + }, + /* * By default, CryptPad also contacts our accounts server once a day to check for changes in * the people who have accounts. This check-in will also send the version of your CryptPad diff --git a/customize.dist/404.html b/customize.dist/404.html new file mode 100644 index 000000000..9298a5377 --- /dev/null +++ b/customize.dist/404.html @@ -0,0 +1,16 @@ + + + +
+
en
';
- out.bottom_support = 'Un projet
Labs avec le soutien de
';
+ // 404 page
+ out.four04_pageNotFound = "Nous n'avons pas trouvé la page que vous cherchez.";
// Header.html
diff --git a/customize.dist/translations/messages.js b/customize.dist/translations/messages.js
index 36a94307a..90ce1871e 100644
--- a/customize.dist/translations/messages.js
+++ b/customize.dist/translations/messages.js
@@ -302,6 +302,8 @@ define(function () {
out.profile_namePlaceholder = 'Name displayed in your profile';
out.profile_avatar = "Avatar";
out.profile_upload = " Upload a new avatar";
+ out.profile_uploadSizeError = "Error: your avatar must be smaller than {0}";
+ out.profile_uploadTypeError = "Error: your avatar type is not allowed. Allowed types are: {0}";
out.profile_error = "Error while creating your profile: {0}";
out.profile_register = "You have to sign up to create a profile!";
out.profile_create = "Create a profile";
@@ -689,10 +691,13 @@ define(function () {
out.tos_logs = "Metadata provided by your browser to the server may be logged for the purpose of maintaining the service.";
out.tos_3rdparties = "We do not provide individualized data to third parties unless required to by law.";
+ // 404 page
+ out.four04_pageNotFound = "We couldn't find the page you were looking for.";
+
// BottomBar.html
- out.bottom_france = 'Made with
in
';
- out.bottom_support = 'An
Labs Project with the support of
';
+ //out.bottom_france = 'Made with
in
';
+ //out.bottom_support = 'An
Labs Project with the support of
';
// Header.html
diff --git a/rpc.js b/rpc.js
index fc87bde58..60bc00ac3 100644
--- a/rpc.js
+++ b/rpc.js
@@ -427,6 +427,28 @@ var updateLimits = function (config, publicKey, cb /*:(?string, ?any[])=>void*/)
"Content-Length": Buffer.byteLength(body)
}
};
+
+ // read custom limits from the config
+ var customLimits = (function (custom) {
+ var limits = {};
+ Object.keys(custom).forEach(function (k) {
+ k.replace(/\/([^\/]+)$/, function (all, safeKey) {
+ var id = unescapeKeyCharacters(safeKey || '');
+ limits[id] = custom[k];
+ return '';
+ });
+ });
+ return limits;
+ }(config.customLimits || {}));
+
+ var isLimit = function (o) {
+ var valid = o && typeof(o) === 'object' &&
+ typeof(o.limit) === 'number' &&
+ typeof(o.plan) === 'string' &&
+ typeof(o.note) === 'string';
+ return valid;
+ };
+
var req = Https.request(options, function (response) {
if (!('' + response.statusCode).match(/^2\d\d$/)) {
return void cb('SERVER ERROR ' + response.statusCode);
@@ -441,6 +463,11 @@ var updateLimits = function (config, publicKey, cb /*:(?string, ?any[])=>void*/)
try {
var json = JSON.parse(str);
limits = json;
+ Object.keys(customLimits).forEach(function (k) {
+ if (!isLimit(customLimits[k])) { return; }
+ limits[k] = customLimits[k];
+ });
+
var l;
if (userId) {
var limit = limits[userId];
diff --git a/server.js b/server.js
index da847ac31..4c64965fe 100644
--- a/server.js
+++ b/server.js
@@ -157,6 +157,22 @@ app.get('/api/config', function(req, res){
].join(';\n'));
});
+var four04_path = Path.resolve(__dirname + '/customize.dist/404.html');
+var custom_four04_path = Path.resolve(__dirname + '/customize/404.html');
+
+var send404 = function (res, path) {
+ if (!path && path !== four04_path) { path = four04_path; }
+ Fs.exists(path, function (exists) {
+ if (exists) { return Fs.createReadStream(path).pipe(res); }
+ send404(res);
+ });
+};
+
+app.use(function (req, res, next) {
+ res.status(404);
+ send404(res, custom_four04_path);
+});
+
var httpServer = httpsOpts ? Https.createServer(httpsOpts, app) : Http.createServer(app);
httpServer.listen(config.httpPort,config.httpAddress,function(){
diff --git a/www/assert/main.js b/www/assert/main.js
index 968200dc9..e88e13fd1 100644
--- a/www/assert/main.js
+++ b/www/assert/main.js
@@ -5,9 +5,11 @@ define([
'/drive/tests.js',
'/common/test.js',
'/common/common-hash.js',
+ '/common/common-util.js',
'/common/common-thumbnail.js',
+ '/common/wire.js',
'/common/flat-dom.js',
-], function ($, Hyperjson, Sortify, Drive, Test, Hash, Thumb, Flat) {
+], function ($, Hyperjson, Sortify, Drive, Test, Hash, Util, Thumb, Wire, Flat) {
window.Hyperjson = Hyperjson;
window.Sortify = Sortify;
@@ -30,7 +32,7 @@ define([
ASSERTS.forEach(function (f, index) {
f(function (err) {
- console.log("test " + index);
+ //console.log("test " + index);
done(err, index);
}, index);
});
@@ -235,6 +237,54 @@ define([
return cb(true);
}, "version 2 hash failed to parse correctly");
+ assert(function (cb) {
+ Wire.create({
+ constructor: function (cb) {
+ var service = function (type, data, cb) {
+ switch (type) {
+ case "HEY_BUDDY":
+ return cb(void 0, "SALUT!");
+ default:
+ cb("ERROR");
+ }
+ };
+
+
+ var evt = Util.mkEvent();
+ var respond = function (e, out) {
+ evt.fire(e, out);
+ };
+ cb(void 0, {
+ send: function (raw /*, cb */) {
+ try {
+ var parsed = JSON.parse(raw);
+ var txid = parsed.txid;
+ var message = parsed.message;
+ setTimeout(function () {
+ service(message.command, message.content, function (e, result) {
+ respond(JSON.stringify({
+ txid: txid,
+ error: e,
+ content: result,
+ }));
+ });
+ });
+ } catch (e) { console.error("PEWPEW"); }
+ },
+ receive: function (f) {
+ evt.reg(f);
+ },
+ });
+ },
+ }, function (e, rpc) {
+ if (e) { return cb(false); }
+ rpc.send('HEY_BUDDY', null, function (e, out) {
+ if (e) { return void cb(false); }
+ if (out === 'SALUT!') { cb(true); }
+ });
+ });
+ }, "Test rpc factory");
+
/*
assert(function (cb) {
var getBlob = function (url, cb) {
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-language.js b/www/common/common-language.js
index 1af06df33..8b27a64bf 100644
--- a/www/common/common-language.js
+++ b/www/common/common-language.js
@@ -10,7 +10,6 @@ define([
// Add handler to the language selector
Msg.setLanguage = function (l, sframeChan, cb) {
- console.log(sframeChan);
if (sframeChan) {
// We're in the sandbox
sframeChan.query("Q_LANGUAGE_SET", l, cb);
diff --git a/www/common/common-messaging.js b/www/common/common-messaging.js
index da5ac004a..13d132219 100644
--- a/www/common/common-messaging.js
+++ b/www/common/common-messaging.js
@@ -1,15 +1,12 @@
define([
- 'jquery',
'/bower_components/chainpad-crypto/crypto.js',
- '/common/curve.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-constants.js',
'/customize/messages.js',
- '/bower_components/marked/marked.min.js',
'/common/common-realtime.js',
-], function ($, Crypto, Curve, Hash, Util, Constants, Messages, Marked, Realtime) {
+], function (Crypto, Hash, Util, Constants, Messages, Realtime) {
var Msg = {
inputs: [],
};
@@ -51,9 +48,8 @@ define([
});
};
- Msg.getFriendChannelsList = function (common) {
+ Msg.getFriendChannelsList = function (proxy) {
var list = [];
- var proxy = common.getProxy();
eachFriend(proxy.friends, function (friend) {
list.push(friend.channel);
});
@@ -61,7 +57,7 @@ define([
};
// TODO make this internal to the messenger
- var channels = Msg.channels = window.channels = {};
+ var channels = Msg.channels = {};
Msg.getLatestMessages = function () {
Object.keys(channels).forEach(function (id) {
@@ -74,8 +70,8 @@ define([
// Invitation
// FIXME there are too many functions with this name
- var addToFriendList = Msg.addToFriendList = function (common, data, cb) {
- var proxy = common.getProxy();
+ var addToFriendList = Msg.addToFriendList = function (cfg, data, cb) {
+ var proxy = cfg.proxy;
var friends = getFriendList(proxy);
var pubKey = data.curvePublic; // todo validata data
@@ -83,19 +79,19 @@ define([
friends[pubKey] = data;
- Realtime.whenRealtimeSyncs(common.getRealtime(), function () {
+ Realtime.whenRealtimeSyncs(cfg.realtime, function () {
cb();
- common.pinPads([data.channel], function (e) {
- if (e) { console.error(e); }
+ cfg.pinPads([data.channel], function (res) {
+ if (res.error) { console.error(res.error); }
});
});
- common.changeDisplayName(proxy[Constants.displayNameKey]);
+ cfg.updateMetadata();
};
/* Used to accept friend requests within apps other than /contacts/ */
- Msg.addDirectMessageHandler = function (common) {
- var network = common.getNetwork();
- var proxy = common.getProxy();
+ Msg.addDirectMessageHandler = function (cfg) {
+ var network = cfg.network;
+ var proxy = cfg.proxy;
if (!network) { return void console.error('Network not ready'); }
network.on('message', function (message, sender) {
var msg;
@@ -138,8 +134,7 @@ define([
var confirmMsg = Messages._getKey('contacts_request', [
Util.fixHTML(msgData.displayName)
]);
- common.onFriendRequest(confirmMsg, todo);
- //UI.confirm(confirmMsg, todo, null, true);
+ cfg.friendRequest(confirmMsg, todo);
return;
}
if (msg[0] === "FRIEND_REQ_OK") {
@@ -147,14 +142,14 @@ define([
if (idx !== -1) { pendingRequests.splice(idx, 1); }
// FIXME clarify this function's name
- addToFriendList(common, msgData, function (err) {
+ addToFriendList(cfg, msgData, function (err) {
if (err) {
- return void common.onFriendComplete({
+ return void cfg.friendComplete({
logText: Messages.contacts_addError,
netfluxId: sender
});
}
- common.onFriendComplete({
+ cfg.friendComplete({
logText: Messages.contacts_added,
netfluxId: sender
});
@@ -167,24 +162,24 @@ define([
if (msg[0] === "FRIEND_REQ_NOK") {
var i = pendingRequests.indexOf(sender);
if (i !== -1) { pendingRequests.splice(i, 1); }
- common.onFriendComplete({
+ cfg.friendComplete({
logText: Messages.contacts_rejected,
netfluxId: sender
});
- common.changeDisplayName(proxy[Constants.displayNameKey]);
+ cfg.updateMetadata();
return;
}
if (msg[0] === "FRIEND_REQ_ACK") {
var data = pending[sender];
if (!data) { return; }
- addToFriendList(common, data, function (err) {
+ addToFriendList(cfg, data, function (err) {
if (err) {
- return void common.onFriendComplete({
+ return void cfg.friendComplete({
logText: Messages.contacts_addError,
netfluxId: sender
});
}
- common.onFriendComplete({
+ cfg.friendComplete({
logText: Messages.contacts_added,
netfluxId: sender
});
@@ -198,17 +193,14 @@ define([
});
};
- Msg.getPending = function () {
- return pendingRequests;
- };
-
- Msg.inviteFromUserlist = function (common, netfluxId) {
- var network = common.getNetwork();
- var parsed = Hash.parsePadUrl(window.location.href);
+ Msg.inviteFromUserlist = function (cfg, data, cb) {
+ var network = cfg.network;
+ var netfluxId = data.netfluxId;
+ var parsed = Hash.parsePadUrl(data.href);
if (!parsed.hashData) { return; }
// Message
var chan = parsed.hashData.channel;
- var myData = createData(common.getProxy());
+ var myData = createData(cfg.proxy);
var msg = ["FRIEND_REQ", chan, myData];
// Encryption
var keyStr = parsed.hashData.key;
@@ -218,12 +210,10 @@ define([
// Send encrypted message
if (pendingRequests.indexOf(netfluxId) === -1) {
pendingRequests.push(netfluxId);
- var proxy = common.getProxy();
- // this redraws the userlist after a change has occurred
- // TODO rename this function to reflect its purpose
- common.changeDisplayName(proxy[Constants.displayNameKey]);
+ cfg.updateMetadata(); // redraws the userlist in pad
}
network.sendto(netfluxId, msgStr);
+ cb();
};
return Msg;
diff --git a/www/common/common-messenger.js b/www/common/common-messenger.js
index 406b27482..cd0a5626d 100644
--- a/www/common/common-messenger.js
+++ b/www/common/common-messenger.js
@@ -1,11 +1,11 @@
define([
- 'jquery',
'/bower_components/chainpad-crypto/crypto.js',
'/common/curve.js',
'/common/common-hash.js',
'/common/common-util.js',
'/common/common-realtime.js',
-], function ($, Crypto, Curve, Hash, Util, Realtime) {
+ '/common/common-constants.js',
+], function (Crypto, Curve, Hash, Util, Realtime, Constants) {
'use strict';
var Msg = {
inputs: [],
@@ -28,7 +28,7 @@ define([
var createData = Msg.createData = function (proxy, hash) {
return {
channel: hash || Hash.createChannelId(),
- displayName: proxy['cryptpad.username'],
+ displayName: proxy[Constants.displayNameKey],
profile: proxy.profile && proxy.profile.view,
edPublic: proxy.edPublic,
curvePublic: proxy.curvePublic,
@@ -56,7 +56,7 @@ define([
});
};
- Msg.messenger = function (common) {
+ Msg.messenger = function (store) {
var messenger = {
handlers: {
message: [],
@@ -89,9 +89,9 @@ define([
var joining = {};
// declare common variables
- var network = common.getNetwork();
- var proxy = common.getProxy();
- var realtime = common.getRealtime();
+ var network = store.network;
+ var proxy = store.proxy;
+ var realtime = store.realtime;
Msg.hk = network.historyKeeper;
var friends = getFriendList(proxy);
@@ -484,7 +484,7 @@ define([
};
var msg = ['GET_HISTORY', chan.id, cfg];
network.sendto(network.historyKeeper, JSON.stringify(msg))
- .then($.noop, function (err) {
+ .then(function () {}, function (err) {
throw new Error(err);
});
};
@@ -629,14 +629,7 @@ define([
messenger.getMyInfo = function (cb) {
cb(void 0, {
curvePublic: proxy.curvePublic,
- displayName: common.getDisplayName(),
- });
- };
-
- messenger.clearOwnedChannel = function (channel, cb) {
- common.clearOwnedChannel(channel, function (e) {
- if (e) { return void cb(e); }
- cb();
+ displayName: proxy[Constants.displayNameKey]
});
};
diff --git a/www/common/common-realtime.js b/www/common/common-realtime.js
index 6480d7e6b..21c0728e2 100644
--- a/www/common/common-realtime.js
+++ b/www/common/common-realtime.js
@@ -1,18 +1,6 @@
-define([
- '/customize/application_config.js',
- '/customize/messages.js',
- '/common/common-interface.js',
-], function (AppConfig, Messages, UI) {
+define([], function () {
var common = {};
- common.infiniteSpinnerDetected = false;
- var BAD_STATE_TIMEOUT = typeof(AppConfig.badStateTimeout) === 'number'?
- AppConfig.badStateTimeout: 30000;
-
- var connected = false;
- var intr;
- var infiniteSpinnerHandlers = [];
-
/*
TODO make this not blow up when disconnected or lagging...
*/
@@ -20,7 +8,7 @@ define([
if (typeof(realtime.getAuthDoc) !== 'function') {
return void console.error('improper use of this function');
}
- window.setTimeout(function () {
+ setTimeout(function () {
if (realtime.getAuthDoc() === realtime.getUserDoc()) {
return void cb();
} else {
@@ -29,36 +17,5 @@ define([
}, 0);
};
- common.beginDetectingInfiniteSpinner = function (realtime) {
- if (intr) { return; }
- intr = window.setInterval(function () {
- var l;
- try {
- l = realtime.getLag();
- } catch (e) {
- throw new Error("ChainPad.getLag() does not exist, please `bower update`");
- }
- if (l.lag < BAD_STATE_TIMEOUT || !connected) { return; }
- realtime.abort();
- // don't launch more than one popup
- if (common.infiniteSpinnerDetected) { return; }
- infiniteSpinnerHandlers.forEach(function (ish) { ish(); });
-
- // inform the user their session is in a bad state
- UI.confirm(Messages.realtime_unrecoverableError, function (yes) {
- if (!yes) { return; }
- window.parent.location.reload();
- });
- common.infiniteSpinnerDetected = true;
- }, 2000);
- };
-
- common.onInfiniteSpinner = function (f) { infiniteSpinnerHandlers.push(f); };
-
- common.setConnectionState = function (bool) {
- if (typeof(bool) !== 'boolean') { return; }
- connected = bool;
- };
-
return common;
});
diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js
index 57392a5cd..c060b0cdc 100644
--- a/www/common/common-ui-elements.js
+++ b/www/common/common-ui-elements.js
@@ -98,7 +98,13 @@ define([
target: data.target
};
if (data.filter && !data.filter(file)) {
- UI.log('Invalid avatar (type or size)');
+ return;
+ }
+ if (data.transformer) {
+ data.transformer(file, function (newFile) {
+ data.FM.handleFile(newFile, ev);
+ if (callback) { callback(); }
+ });
return;
}
data.FM.handleFile(file, ev);
@@ -571,12 +577,7 @@ define([
// getPinnedUsage updates common.account.usage, and other values
// so we can just use those and only check for errors
var $container = $('', {'class':'cp-limit-container'});
- var todo;
- var updateUsage = Util.notAgainForAnother(function () {
- common.getPinUsage(todo);
- }, LIMIT_REFRESH_RATE);
-
- todo = function (err, data) {
+ var todo = function (err, data) {
if (err) { return void console.error(err); }
var usage = data.usage;
@@ -645,6 +646,10 @@ define([
$limit.append($usage).append($text);
};
+ var updateUsage = Util.notAgainForAnother(function () {
+ common.getPinUsage(todo);
+ }, LIMIT_REFRESH_RATE);
+
setInterval(function () {
updateUsage();
}, LIMIT_REFRESH_RATE * 3);
diff --git a/www/common/common-util.js b/www/common/common-util.js
index 759730663..030536751 100644
--- a/www/common/common-util.js
+++ b/www/common/common-util.js
@@ -209,6 +209,43 @@ define([], function () {
xhr.send();
};
+ // Check if an element is a plain object
+ Util.isObject = function (o) {
+ return typeof (o) === "object" &&
+ Object.prototype.toString.call(o) === '[object Object]';
+ };
+
+ Util.isCircular = function (o) {
+ try {
+ JSON.stringify(o);
+ return false;
+ } catch (e) { return true; }
+ };
+
+ /* recursively adds the properties of an object 'b' to 'a'
+ arrays are only shallow copies, so references to the original
+ might still be present. Be mindful if you will modify 'a' in the future */
+ Util.extend = function (a, b) {
+ if (!Util.isObject(a) || !Util.isObject(b)) {
+ return void console.log("Extend only works with 2 objects");
+ }
+ if (Util.isCircular(b)) {
+ return void console.log("Extend doesn't accept circular objects");
+ }
+ for (var k in b) {
+ if (Util.isObject(b[k])) {
+ a[k] = {};
+ Util.extend(a[k], b[k]);
+ continue;
+ }
+ if (Array.isArray(b[k])) {
+ a[k] = b[k].slice();
+ continue;
+ }
+ a[k] = b[k];
+ }
+ };
+
return Util;
});
}(self));
diff --git a/www/common/cryptget.js b/www/common/cryptget.js
index 7a663480e..686c69884 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 = setTimeout(function () {
cb(new Error("Timeout"));
}, 5000);
Realtime.whenRealtimeSyncs(realtime, function () {
- window.clearTimeout(to);
+ clearTimeout(to);
realtime.abort();
finish(Session, void 0);
});
diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js
index 181f6b7ae..68a17a5c2 100644
--- a/www/common/cryptpad-common.js
+++ b/www/common/cryptpad-common.js
@@ -1,22 +1,20 @@
define([
- 'jquery',
'/api/config',
'/customize/messages.js',
- '/common/fsStore.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-messaging.js',
'/common/common-realtime.js',
- '/common/common-language.js',
'/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,
+], function (Config, Messages, Util, Hash,
+ Messaging, Realtime, Constants, Feedback, LocalStore, AStore,
Pinpad, AppConfig, Nthen) {
/* This file exposes functionality which is specific to Cryptpad, but not to
@@ -25,6 +23,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 = {
@@ -36,619 +46,123 @@ define([
var PINNING_ENABLED = AppConfig.enablePinning;
- var store;
- 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) {
- return store.getProxy().info.network;
- }
- }
- return;
- };
-
- // REFACTOR pull language directly
+ // COMMON
common.getLanguage = function () {
return Messages._languageUsed;
};
common.setLanguage = function (l, cb) {
- Language.setLanguage(l, null, cb);
+ var LS_LANG = "CRYPTPAD_LANG";
+ localStorage.setItem(LS_LANG, l);
+ cb();
};
- // 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;
- }
- };
-
- var getRealtime = common.getRealtime = function () {
- if (store && store.getProxy() && store.getProxy().info) {
- return store.getProxy().info.realtime;
- }
- return;
- };
-
- common.hasSigningKeys = function (proxy) {
- return typeof(proxy) === 'object' &&
- typeof(proxy.edPrivate) === 'string' &&
- typeof(proxy.edPublic) === 'string';
- };
-
- 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 {
- href: href,
- atime: now,
- ctime: now,
- title: title || Hash.getDefaultName(Hash.parsePadUrl(href)),
- };
- };
-
- // 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); }
+ // RESTRICTED
+ // Settings only
+ common.getUserObject = function (cb) {
+ postMessage("GET", [], function (obj) {
+ cb(obj);
});
};
-
- // STORAGE
- common.getPadAttribute = function (attr, cb) {
- var href = Hash.getRelativeHref(window.location.href);
- getStore().getPadAttribute(href, attr, cb);
- };
- common.getAttribute = function (attr, cb) {
- getStore().getAttribute(attr, function (err, data) {
- cb(err, data);
- });
- };
-
-
- /* 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);
- };
-
- common.resetTags = function (href, tags, cb) {
- 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();
+ common.resetDrive = function (cb) {
+ postMessage("RESET_DRIVE", null, function (obj) {
+ if (obj.error) { return void cb(obj.error); }
cb();
});
};
-
- 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) {
- 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);
- }
+ 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();
});
};
-
- 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) {
- if (e) { return void cb(e); }
- if (!entry) { cb('NO_ENTRY'); }
- if (!entry.tags) { return void cb(); }
- var idx = entry.tags.indexOf(tag);
- if (idx === -1) { return void cb(); }
- entry.tags.splice(idx, 1);
- cb();
+ // Settings and auth
+ common.getUserObject = function (cb) {
+ postMessage("GET", [], function (obj) {
+ cb(obj);
});
};
-
- 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.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);
- }
- });
- });
- 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;
- });
- 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.isTemplate = function (href) {
- var rhref = Hash.getRelativeHref(href);
- var templates = listTemplates();
- return 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"); }
- Crypt.get(parsed.hash, function (err, val) {
- if (err) { throw new Error(err); }
- var p = Hash.parsePadUrl(window.location.href);
- Crypt.put(p.hash, val, cb);
- });
- };
-
- // 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");
- };
-
- common.setPadTitle = function (name, padHref, cb) {
- var href = typeof padHref === "string" ? 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() === "") {
- var parsed = Hash.parsePadUrl(href || window.location.href);
- title = Hash.getDefaultName(parsed);
- }
-
- common.setPadTitle(title, href, function (err) {
- if (err) {
- console.log("unable to set pad title");
- console.error(err);
- return;
- }
- callback(null, title);
- });
- };
-
- // 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;
+ // Settings and ready
+ common.mergeAnonDrive = function (cb) {
+ var data = {
+ anonHash: LocalStore.getFSHash()
};
- 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;
- }
- });
- cb (null, list);
+ postMessage("MIGRATE_ANON_DRIVE", data, cb);
};
-
- 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);
+ // Profile
+ common.getProfileEditUrl = function (cb) {
+ postMessage("GET", ['profile', 'edit'], function (obj) {
+ cb(obj);
});
};
-
- 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);
+ 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) {
- 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, function (obj) {
+ if (obj && obj.error) { return void cb(obj.error); }
+ cb(null, obj.hash);
});
};
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, function (obj) {
+ if (obj && obj.error) { return void cb(obj.error); }
+ cb(null, obj.hash);
});
};
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);
- });
- };
-
- // 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);
- };
-
- 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');
- }
- });
- };
-
- 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_PINNED_USAGE", null, function (obj) {
+ if (obj.error) { return void cb(obj.error); }
+ cb(null, obj.bytes);
});
};
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,50 +185,187 @@ define([
};
common.clearOwnedChannel = function (channel, cb) {
- if (!pinsReady()) { return void cb('RPC_NOT_READY'); }
- rpc.clearOwnedChannel(channel, cb);
+ postMessage("CLEAR_OWNED_CHANNEL", channel, cb);
};
common.uploadComplete = function (cb) {
- if (!pinsReady()) { return void cb('RPC_NOT_READY'); }
- rpc.uploadComplete(cb);
- };
-
- common.uploadStatus = function (size, cb) {
- if (!pinsReady()) { return void cb('RPC_NOT_READY'); }
- rpc.uploadStatus(size, 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_COMPLETE", null, 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 (cb) {
+ postMessage("UPLOAD_CANCEL", null, 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);
+ });
+ };
+
+ // 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, cb) {
+ postMessage("GET_FILE_SIZE", {href: href}, 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);
+ });
+ };
+
+ // 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) {
+ 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) {
+ var href = Hash.getRelativeHref(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) {
+ 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);
});
};
- // 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; }
@@ -722,36 +373,141 @@ define([
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 () {
+ 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) {
+ var parsed = Hash.parsePadUrl(href);
+ if(!parsed) { throw new Error("Cannot get template hash"); }
+ Crypt.get(parsed.hash, function (err, val) {
+ if (err) { throw new Error(err); }
+ var p = Hash.parsePadUrl(window.location.href);
+ Crypt.put(p.hash, val, cb);
+ });
+ };
+
+ // 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 (title, padHref, path, cb) {
+ var href = padHref || window.location.href;
+ var parsed = Hash.parsePadUrl(href);
+ if (!parsed.hash) { return; }
+ href = parsed.getUrl({present: parsed.present});
+
+ if (title === null) { return; }
+ if (title.trim() === "") { title = Hash.getDefaultName(parsed); }
+
+ postMessage("SET_PAD_TITLE", {
+ href: href,
+ title: title,
+ path: path
+ }, 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);
+ });
+ };
+
+ // 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();
+ });
+ };
+
+ // 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();
+
+ // HERE
common.getShareHashes = function (secret, cb) {
+ var hashes;
if (!window.location.hash) {
- var hashes = Hash.getHashes(secret.channel, secret);
+ 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);
+ 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); }
+ 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;
- }
+ 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: window.location.href
+ }, function (hash) {
+ if (hash) { hashes.editHash = hash; }
cb(null, hashes);
});
};
@@ -777,27 +533,84 @@ 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;
+ }
+ case 'Q_FRIEND_REQUEST': {
+ if (!common.onFriendRequest) { break; }
+ common.onFriendRequest(data, cb);
+ break;
+ }
+ case 'EV_FRIEND_COMPLETE': {
+ if (!common.onFriendComplete) { break; }
+ common.onFriendComplete(data);
+ break;
+ }
+ // Messenger
+ case 'CONTACTS_MESSAGE': {
+ common.messenger.onMessageEvent.fire(data); break;
+ }
+ case 'CONTACTS_JOIN': {
+ common.messenger.onJoinEvent.fire(data); break;
+ }
+ case 'CONTACTS_LEAVE': {
+ common.messenger.onLeaveEvent.fire(data); break;
+ }
+ case 'CONTACTS_UPDATE': {
+ common.messenger.onUpdateEvent.fire(data); break;
+ }
+ case 'CONTACTS_FRIEND': {
+ common.messenger.onFriendEvent.fire(data); break;
+ }
+ case 'CONTACTS_UNFRIEND': {
+ common.messenger.onUnfriendEvent.fire(data); break;
+ }
+ }
+ };
+
common.ready = (function () {
var env = {};
var initialized = false;
- return function (f) {
+ return function (f, rdyCfg) {
+ rdyCfg = rdyCfg || {};
if (initialized) {
return void setTimeout(function () { f(void 0, env); });
}
- 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,38 +630,48 @@ 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);
- }).nThen(function (waitFor) {
- $(waitFor());
+ var cfg = {
+ query: onMessage, // TODO temporary, will be replaced by a webworker channel
+ userHash: LocalStore.getUserHash(),
+ anonHash: LocalStore.getFSHash(),
+ localToken: tryParsing(localStorage.getItem(Constants.tokenKey)),
+ language: common.getLanguage(),
+ messenger: rdyCfg.messenger
+ };
+ if (sessionStorage[Constants.newPadPathKey]) {
+ cfg.initialPath = sessionStorage[Constants.newPadPathKey];
+ delete sessionStorage[Constants.newPadPathKey];
+ }
+ 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) {
// Load the new pad when the hash has changed
var oldHref = document.location.href;
@@ -876,114 +699,42 @@ 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 (obj) {
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');
- });
- }
- });
- });
+ 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');
}
- 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();
@@ -994,10 +745,5 @@ define([
}());
- // MAGIC that happens implicitly
- $(function () {
- Language.applyTranslation();
- });
-
return common;
});
diff --git a/www/common/dom-ready.js b/www/common/dom-ready.js
new file mode 100644
index 000000000..69a484493
--- /dev/null
+++ b/www/common/dom-ready.js
@@ -0,0 +1,10 @@
+define(function () {
+ return {
+ onReady: function (cb) {
+ if (document.readyState === 'complete') { return void cb(); }
+ document.onreadystatechange = function () {
+ if (document.readyState === 'complete') { cb(); }
+ };
+ }
+ };
+});
diff --git a/www/common/flat-dom.js b/www/common/flat-dom.js
index 51c3d07b3..25b51d786 100644
--- a/www/common/flat-dom.js
+++ b/www/common/flat-dom.js
@@ -33,7 +33,7 @@ define([], function () {
data.map[id] = el.textContent;
return id;
}
- if (!el || !el.attributes) { return void console.error(el); }
+ if (!el || !el.attributes) { return; }
id = uid();
data.map[id] = [
el.tagName,
diff --git a/www/common/fsStore.js b/www/common/fsStore.js
deleted file mode 100644
index 6ef2f6d51..000000000
--- a/www/common/fsStore.js
+++ /dev/null
@@ -1,359 +0,0 @@
-define([
- 'jquery',
- '/bower_components/chainpad-listmap/chainpad-listmap.js',
- '/bower_components/chainpad-crypto/crypto.js?v=0.1.5',
- '/common/userObject.js',
- '/common/common-interface.js',
- '/common/common-hash.js',
- '/common/common-util.js',
- '/common/common-constants.js',
- '/common/migrate-user-object.js',
- '/bower_components/chainpad/chainpad.dist.js',
- '/common/outer/network-config.js',
- '/common/outer/local-store.js',
-], function ($, Listmap, Crypto, FO, UI, Hash, Util, Constants, Migrate, ChainPad, NetConfig,
- LocalStore) {
- /*
- This module uses localStorage, which is synchronous, but exposes an
- asyncronous API. This is so that we can substitute other storage
- methods.
-
- To override these methods, create another file at:
- /customize/storage.js
- */
-
- var Store = {};
- var store;
-
- var initStore = function (filesOp, storeObj, exp) {
- var ret = {};
-
- var safeSet = function (key, val) {
- storeObj[key] = val;
- };
-
- // Store uses nodebacks...
- ret.set = function (key, val, cb) {
- safeSet(key, val);
- cb();
- };
-
- // implement in alternative store
- ret.setBatch = function (map, cb) {
- Object.keys(map).forEach(function (key) {
- safeSet(key, map[key]);
- });
- cb(void 0, map);
- };
-
- ret.setDrive = function (key, val, cb) {
- storeObj.drive[key] = val;
- cb();
- };
-
- var safeGet = function (key) {
- return storeObj[key];
- };
-
- ret.get = function (key, cb) {
- cb(void 0, safeGet(key));
- };
-
- // implement in alternative store
- ret.getBatch = function (keys, cb) {
- var res = {};
- keys.forEach(function (key) {
- res[key] = safeGet(key);
- });
- cb(void 0, res);
- };
-
- 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]
- };
- };
- ret.setAttribute = function (attr, value, cb) {
- try {
- var object = getAttributeObject(attr);
- object.obj[object.key] = value;
- } catch (e) { return void cb(e); }
- cb();
- };
- ret.getAttribute = function (attr, cb) {
- var object;
- try {
- object = getAttributeObject(attr);
- } catch (e) { return void cb(e); }
- cb(null, object.obj[object.key]);
- };
- ret.setPadAttribute = filesOp.setPadAttribute;
- ret.getPadAttribute = filesOp.getPadAttribute;
- ret.getIdFromHref = filesOp.getIdFromHref;
-
- ret.getDrive = function (key, cb) {
- cb(void 0, storeObj.drive[key]);
- };
-
- var safeRemove = function (key) {
- delete storeObj[key];
- };
-
- ret.remove = function (key, cb) {
- safeRemove(key);
- cb();
- };
-
- // implement in alternative store
- ret.removeBatch = function (keys, cb) {
- keys.forEach(function (key) {
- safeRemove(key);
- });
- cb();
- };
-
- ret.keys = function (cb) {
- cb(void 0, Object.keys(storeObj));
- };
-
- ret.removeData = filesOp.removeData;
- ret.pushData = filesOp.pushData;
- ret.addPad = filesOp.add;
-
- ret.forgetPad = function (href, cb) {
- filesOp.forget(href);
- cb();
- };
-
- ret.listTemplates = function () {
- 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;
- };
-
- ret.getProxy = function () {
- return exp;
- };
-
- ret.getLoginName = function () {
- return storeObj.login_name;
- };
-
- ret.repairDrive = function () {
- filesOp.fixFiles();
- };
-
- ret.getEmptyObject = function () {
- return filesOp.getStructure();
- };
-
- ret.replace = filesOp.replace;
-
- ret.restoreHref = filesOp.restoreHref;
-
- ret.changeHandlers = [];
-
- ret.change = function () {};
-
- ret.getProfile = function () {
- return storeObj.profile;
- };
-
- return ret;
- };
-
- var tryParsing = function (x) {
- try { return JSON.parse(x); }
- catch (e) {
- console.error(e);
- return null;
- }
- };
-
- var onReady = function (f, proxy, Cryptpad, exp) {
- var fo = exp.fo = FO.init(proxy.drive, {
- Cryptpad: Cryptpad,
- loggedIn: LocalStore.isLoggedIn()
- });
- var todo = function () {
- fo.fixFiles();
-
- Migrate(proxy, Cryptpad);
-
- store = initStore(fo, proxy, exp);
- if (typeof(f) === 'function') {
- f(void 0, store);
- }
- //storeObj = proxy;
-
- 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 tokenKey = 'loginToken';
- if (LocalStore.isLoggedIn()) {
- /* 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[tokenKey] = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
- }
-
- // copy User_hash into sessionStorage because cross-domain iframes
- // on safari replaces localStorage with sessionStorage or something
- if (sessionStorage) { sessionStorage.setItem('User_hash', localStorage.getItem('User_hash')); }
-
- var localToken = tryParsing(localStorage.getItem(tokenKey));
- if (localToken === null) {
- // if that number hasn't been set to localStorage, do so.
- localStorage.setItem(tokenKey, proxy.loginToken);
- } else if (localToken !== proxy[tokenKey]) {
- // if it has been, and 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;
- }
-
- 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 (LocalStore.isLoggedIn() && (!Cryptpad.hasSigningKeys(proxy) ||
- !Cryptpad.hasCurveKeys(proxy))) {
- return void requestLogin();
- }
-
- proxy.on('change', [Constants.displayNameKey], function (o, n) {
- if (typeof(n) !== "string") { return; }
- Cryptpad.changeDisplayName(n);
- });
- proxy.on('change', ['profile'], function () {
- // Trigger userlist update when the avatar has changed
- Cryptpad.changeDisplayName(proxy[Constants.displayNameKey]);
- });
- proxy.on('change', ['friends'], function () {
- // Trigger userlist update when the avatar has changed
- Cryptpad.changeDisplayName(proxy[Constants.displayNameKey]);
- });
- proxy.on('change', [tokenKey], function () {
- var localToken = tryParsing(localStorage.getItem(tokenKey));
- if (localToken !== proxy[tokenKey]) {
- return void requestLogin();
- }
- });
- };
- fo.migrate(todo);
- };
-
- var initialized = false;
-
- var init = function (f, Cryptpad) {
- if (!Cryptpad || initialized) { return; }
- initialized = true;
-
- var hash = LocalStore.getUserHash() || LocalStore.getFSHash() || Hash.createRandomHash();
- 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 exp = {};
-
- var rt = window.rt = Listmap.create(listmapConfig);
-
- exp.realtime = rt.realtime;
- exp.proxy = rt.proxy;
- rt.proxy.on('create', function (info) {
- exp.info = info;
- if (!LocalStore.getUserHash()) {
- LocalStore.setFSHash(Hash.getEditHashFromKeys(info.channel, secret.keys));
- }
- }).on('ready', function () {
- if (store) { 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] = [];
- onReady(f, rt.proxy, Cryptpad, exp);
- return;
- }
- // Drive already exist: return the existing drive, don't load data from legacy store
- onReady(f, rt.proxy, Cryptpad, exp);
- })
- .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();
- UI.alert(Cryptpad.Messages.fs_migration, null, true);
- }
- });
- };
-
- Store.ready = function (f, Cryptpad) {
- if (store) { // Store.ready probably called twice, store already ready
- if (typeof(f) === 'function') {
- f(void 0, store);
- }
- } else {
- init(f, Cryptpad);
- }
- };
-
- return Store;
-});
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/metadata-manager.js b/www/common/metadata-manager.js
index 0e375b736..bf99e4b84 100644
--- a/www/common/metadata-manager.js
+++ b/www/common/metadata-manager.js
@@ -59,7 +59,6 @@ define(['json.sortify'], function (Sortify) {
}
if (metadataObj.title !== rememberedTitle) {
- console.log("Title update\n" + metadataObj.title + '\n');
rememberedTitle = metadataObj.title;
titleChangeHandlers.forEach(function (f) { f(metadataObj.title); });
}
@@ -73,30 +72,45 @@ define(['json.sortify'], function (Sortify) {
});
};
+ var netfluxId;
+ var isReady = false;
+ var readyHandlers = [];
sframeChan.on('EV_METADATA_UPDATE', function (ev) {
meta = ev;
if (ev.priv) {
priv = ev.priv;
}
+ if (netfluxId) {
+ meta.user.netfluxId = netfluxId;
+ }
+ if (!isReady) {
+ isReady = true;
+ readyHandlers.forEach(function (f) { f(); });
+ }
change(true);
});
sframeChan.on('EV_RT_CONNECT', function (ev) {
- meta.user.netfluxId = ev.myID;
+ netfluxId = ev.myID;
members = ev.members;
+ if (!meta.user) { return; }
+ meta.user.netfluxId = netfluxId;
change(true);
});
sframeChan.on('EV_RT_JOIN', function (ev) {
members.push(ev);
+ if (!meta.user) { return; }
change(false);
});
sframeChan.on('EV_RT_LEAVE', function (ev) {
var idx = members.indexOf(ev);
if (idx === -1) { console.log('Error: ' + ev + ' not in members'); return; }
members.splice(idx, 1);
+ if (!meta.user) { return; }
change(false);
});
sframeChan.on('EV_RT_DISCONNECT', function () {
members = [];
+ if (!meta.user) { return; }
change(true);
});
@@ -140,6 +154,10 @@ define(['json.sortify'], function (Sortify) {
},
getNetfluxId : function () {
return meta.user.netfluxId;
+ },
+ onReady: function (f) {
+ if (isReady) { return void f(); }
+ readyHandlers.push(f);
}
});
};
diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js
new file mode 100644
index 000000000..7415843b1
--- /dev/null
+++ b/www/common/outer/async-store.js
@@ -0,0 +1,924 @@
+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/common-messaging.js',
+ '/common/common-messenger.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, Messaging, Messenger,
+ NetConfig,
+ Crypto, ChainPad, Listmap) {
+ var Store = {};
+
+ var postMessage = function () {};
+
+ var storeHash;
+
+ var store = {};
+
+
+ var onSync = function (cb) {
+ Realtime.whenRealtimeSyncs(store.realtime, cb);
+ };
+
+
+ Store.get = function (key, cb) {
+ cb(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'}); }
+ if (typeof data.value === "undefined") {
+ delete obj[key];
+ } else {
+ 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); }
+ }
+
+ if (store.proxy.friends) {
+ var fList = Messaging.getFriendChannelsList(store.proxy);
+ list = list.concat(fList);
+ }
+
+ list.push(Util.base64ToHex(userChannel));
+ list.sort();
+
+ return list;
+ };
+
+ var getCanonicalChannelList = function () {
+ return Util.deduplicateString(getUserChannelList()).sort();
+ };
+
+ //////////////////////////////////////////////////////////////////
+ /////////////////////// RPC //////////////////////////////////////
+ //////////////////////////////////////////////////////////////////
+
+ 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, 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, 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 (data, 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 (data, 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'}); }
+ store.rpc.clearOwnedChannel(data, function (err) {
+ cb({error:err});
+ });
+ };
+
+ Store.uploadComplete = function (data, cb) {
+ if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
+ store.rpc.uploadComplete(function (err, res) {
+ if (err) { return void cb({error:err}); }
+ cb(res);
+ });
+ };
+
+ Store.uploadStatus = function (data, cb) {
+ if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
+ store.rpc.uploadStatus(data.size, function (err, res) {
+ if (err) { return void cb({error:err}); }
+ cb(res);
+ });
+ };
+
+ Store.uploadCancel = function (data, cb) {
+ if (!store.rpc) { return void cb({error: 'RPC_NOT_READY'}); }
+ store.rpc.uploadCancel(function (err, res) {
+ if (err) { return void cb({error:err}); }
+ cb(res);
+ });
+ };
+
+ var 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(e); }
+ cb(null, hash === local);
+ });
+ };
+
+ var resetPins = function (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(e); }
+ cb(null, hash);
+ });
+ };
+
+ Store.uploadChunk = function (data, cb) {
+ store.rpc.send.unauthenticated('UPLOAD', data.chunk, function (e, msg) {
+ cb({
+ error: e,
+ msg: msg
+ });
+ });
+ };
+
+ 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;
+
+ Store.getPinLimit(null, function (obj) {
+ if (obj.error) { console.error(obj.error); }
+ account.limit = obj.limit;
+ account.plan = obj.plan;
+ account.note = obj.note;
+ cb(obj);
+ });
+
+ arePinsSynced(function (err, yes) {
+ if (!yes) {
+ 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, function (err, res) {
+ if (err) { return void cb({error: err}); }
+ cb(res);
+ });
+ };
+
+ Store.getFileSize = function (data, cb) {
+ console.log(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,
+ },
+ // "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: !Util.find(store.proxy, ['settings', 'general', 'disableThumbnails'])
+ }
+ };
+ cb(JSON.parse(JSON.stringify(metadata)));
+ };
+
+ var 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}); }
+ var path = data.path || ['root'];
+ store.userObject.add(id, path);
+ 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: store.proxy.settings,
+ key: attr
+ };
+ }
+ if (!Array.isArray(attr)) { return void console.error("Attribute must be string or array"); }
+ if (attr.length === 0) { return void console.error("Attribute can't be empty"); }
+ var obj = store.proxy.settings;
+ attr.forEach(function (el, i) {
+ if (i === attr.length-1) { return; }
+ if (!obj[el]) {
+ obj[el] = {};
+ }
+ else if (typeof obj[el] !== "object") { return void console.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);
+ };
+
+ // Reset the drive part of the userObject (from settings)
+ Store.resetDrive = function (data, cb) {
+ store.proxy.drive = store.fo.getStructure();
+ onSync(cb);
+ };
+
+ /**
+ * Settings & pad attributes
+ * data
+ * - href (String)
+ * - attr (Array)
+ * - value (String)
+ */
+ Store.setPadAttribute = function (data, cb) {
+ store.userObject.setPadAttribute(data.href, data.attr, data.value, function () {
+ onSync(cb);
+ });
+ };
+ Store.getPadAttribute = function (data, cb) {
+ store.userObject.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);
+ };
+
+ // Templates
+ Store.getTemplates = function (data, cb) {
+ var templateFiles = store.userObject.getFiles(['template']);
+ var res = [];
+ templateFiles.forEach(function (f) {
+ var data = store.userObject.getFileData(f);
+ res.push(JSON.parse(JSON.stringify(data)));
+ });
+ cb(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 = allPads[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(href);
+ }
+
+ // Add the pad if it does not exist in our drive
+ if (!contains) {
+ Store.addPad({
+ href: href,
+ title: title,
+ path: data.path || (store.data && store.data.initialPath)
+ }, 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);
+ };
+
+ // Messaging (manage friends from the userlist)
+ var getMessagingCfg = function () {
+ return {
+ proxy: store.proxy,
+ realtime: store.realtime,
+ network: store.network,
+ updateMetadata: function () {
+ postMessage("UPDATE_METADATA");
+ },
+ pinPads: Store.pinPads,
+ friendComplete: function (data, cb) {
+ postMessage("Q_FRIEND_COMPLETE", data, cb);
+ },
+ friendRequest: function (data) {
+ postMessage("EV_FRIEND_REQUEST", data);
+ },
+ };
+ };
+ Store.inviteFromUserlist = function (data, cb) {
+ var messagingCfg = getMessagingCfg();
+ Messaging.inviteFromUserlist(messagingCfg, data, cb);
+ };
+
+ // Messenger
+
+ // Get hashes for the share button
+ Store.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();
+ };
+
+ Store.messenger = {
+ getFriendList: function (data, cb) {
+ store.messenger.getFriendList(function (e, keys) {
+ cb({
+ error: e,
+ data: keys,
+ });
+ });
+ },
+ getMyInfo: function (data, cb) {
+ store.messenger.getMyInfo(function (e, info) {
+ cb({
+ error: e,
+ data: info,
+ });
+ });
+ },
+ getFriendInfo: function (data, cb) {
+ store.messenger.getFriendInfo(data, function (e, info) {
+ cb({
+ error: e,
+ data: info,
+ });
+ });
+ },
+ removeFriend: function (data, cb) {
+ store.messenger.removeFriend(data, function (e, info) {
+ cb({
+ error: e,
+ data: info,
+ });
+ });
+ },
+ openFriendChannel: function (data, cb) {
+ store.messenger.openFriendChannel(data, function (e) {
+ cb({ error: e, });
+ });
+ },
+ getFriendStatus: function (data, cb) {
+ store.messenger.getStatus(data, function (e, online) {
+ cb({
+ error: e,
+ data: online,
+ });
+ });
+ },
+ getMoreHistory: function (data, cb) {
+ store.messenger.getMoreHistory(data.curvePublic, data.sig, data.count, function (e, history) {
+ cb({
+ error: e,
+ data: history,
+ });
+ });
+ },
+ sendMessage: function (data, cb) {
+ store.messenger.sendMessage(data.curvePublic, data.content, function (e) {
+ cb({
+ error: e,
+ });
+ });
+ },
+ setChannelHead: function (data, cb) {
+ store.messenger.setChannelHead(data.curvePublic, data.sig, function (e) {
+ cb({
+ error: e
+ });
+ });
+ }
+ };
+
+ var onReady = function (returned, cb) {
+ var proxy = store.proxy;
+ var userObject = store.userObject = UserObject.init(proxy.drive, {
+ pinPads: Store.pinPads,
+ loggedIn: store.loggedIn
+ });
+ var todo = function () {
+ userObject.fixFiles();
+
+ Migrate(proxy);
+
+ 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[Constants.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', ['settings'], function () {
+ postMessage("UPDATE_METADATA");
+ });
+ proxy.on('change', [Constants.tokenKey], function () {
+ postMessage("UPDATE_TOKEN", { token: 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.loggedIn = typeof(data.userHash) !== "undefined";
+
+ var returned = {};
+ rt.proxy.on('create', function (info) {
+ store.realtime = info.realtime;
+ store.network = info.network;
+ 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, d, cb) {
+ setTimeout(function () {
+ data.query(cmd, d, cb); // TODO temporary, will be replaced by webworker channel
+ });
+ };
+
+ store.data = data;
+ connect(data, function (ret) {
+ if (Object.keys(store.proxy).length === 1) {
+ Feedback.send("FIRST_APP_USE", true);
+ }
+
+ callback(ret);
+
+ var messagingCfg = getMessagingCfg();
+ Messaging.addDirectMessageHandler(messagingCfg);
+
+ if (data.messenger) {
+ var messenger = store.messenger = Messenger.messenger(store); // TODO
+ messenger.on('message', function (message) {
+ postMessage('CONTACTS_MESSAGE', message);
+ });
+ messenger.on('join', function (curvePublic, channel) {
+ postMessage('CONTACTS_JOIN', {
+ curvePublic: curvePublic,
+ channel: channel,
+ });
+ });
+ messenger.on('leave', function (curvePublic, channel) {
+ postMessage('CONTACTS_LEAVE', {
+ curvePublic: curvePublic,
+ channel: channel,
+ });
+ });
+ messenger.on('update', function (info, curvePublic) {
+ postMessage('CONTACTS_UPDATE', {
+ curvePublic: curvePublic,
+ info: info,
+ });
+ });
+ messenger.on('friend', function (curvePublic) {
+ postMessage('CONTACTS_FRIEND', {
+ curvePublic: curvePublic,
+ });
+ });
+ messenger.on('unfriend', function (curvePublic) {
+ postMessage('CONTACTS_UNFRIEND', {
+ curvePublic: curvePublic,
+ });
+ });
+ }
+ });
+ };
+
+ 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..c0b435079
--- /dev/null
+++ b/www/common/outer/store-rpc.js
@@ -0,0 +1,159 @@
+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_CHUNK': {
+ Store.uploadChunk(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 '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;
+ }
+ // Messaging
+ case 'INVITE_FROM_USERLIST': {
+ Store.inviteFromUserlist(data, cb); break;
+ }
+ // Messenger
+ case 'CONTACTS_GET_FRIEND_LIST': {
+ Store.messenger.getFriendList(data, cb); break;
+ }
+ case 'CONTACTS_GET_MY_INFO': {
+ Store.messenger.getMyInfo(data, cb); break;
+ }
+ case 'CONTACTS_GET_FRIEND_INFO': {
+ Store.messenger.getFriendInfo(data, cb); break;
+ }
+ case 'CONTACTS_REMOVE_FRIEND': {
+ Store.messenger.removeFriend(data, cb); break;
+ }
+ case 'CONTACTS_OPEN_FRIEND_CHANNEL': {
+ Store.messenger.openFriendChannel(data, cb); break;
+ }
+ case 'CONTACTS_GET_FRIEND_STATUS': {
+ Store.messenger.getFriendStatus(data, cb); break;
+ }
+ case 'CONTACTS_GET_MORE_HISTORY': {
+ Store.messenger.getMoreHistory(data, cb); break;
+ }
+ case 'CONTACTS_SEND_MESSAGE': {
+ Store.messenger.sendMessage(data, cb); break;
+ }
+ case 'CONTACTS_SET_CHANNEL_HEAD': {
+ Store.messenger.setChannelHead(data, cb); break;
+ }
+ default: {
+
+ break;
+ }
+ }
+
+ };
+
+ return Rpc;
+});
+
diff --git a/www/common/outer/upload.js b/www/common/outer/upload.js
index 4e42d94da..cce3e6b3c 100644
--- a/www/common/outer/upload.js
+++ b/www/common/outer/upload.js
@@ -20,7 +20,7 @@ define([
var sendChunk = function (box, cb) {
var enc = Nacl.util.encodeBase64(box);
- common.rpc.send.unauthenticated('UPLOAD', enc, function (e, msg) {
+ common.uploadChunk(enc, function (e, msg) {
cb(e, msg);
});
};
@@ -58,8 +58,7 @@ define([
if (noStore) { return void onComplete(href); }
- common.initialPath = path;
- common.renamePad(title || "", href, function (err) {
+ common.setPadTitle(title || "", href, path, function (err) {
if (err) { return void console.error(err); }
onComplete(href);
common.setPadAttribute('fileType', metadata.type, null, href);
diff --git a/www/common/pinpad.js b/www/common/pinpad.js
index ebece2626..47eb97892 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 () {
+ setTimeout(function () {
cb('INVALID_NETWORK');
});
return;
}
if (!proxy) {
- window.setTimeout(function () {
+ setTimeout(function () {
cb('INVALID_PROXY');
});
return;
@@ -19,7 +19,7 @@ define([
var edPublic = proxy.edPublic;
if (!(edPrivate && edPublic)) {
- window.setTimeout(function () {
+ 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 () {
+ 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 () {
+ 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 () {
+ 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 setTimeout(function () {
cb('INVALID_SIZE');
});
}
diff --git a/www/common/rpc.js b/www/common/rpc.js
index 9f04d7663..e86615111 100644
--- a/www/common/rpc.js
+++ b/www/common/rpc.js
@@ -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 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 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 setTimeout(function () {
cb('DISCONNECTED');
});
}
diff --git a/www/common/sframe-app-outer.js b/www/common/sframe-app-outer.js
index 3c675a4c3..d0dcb631c 100644
--- a/www/common/sframe-app-outer.js
+++ b/www/common/sframe-app-outer.js
@@ -2,14 +2,14 @@
define([
'/bower_components/nthen/index.js',
'/api/config',
- 'jquery',
+ '/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js'
-], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) {
+], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
nThen(function (waitFor) {
- $(waitFor());
+ DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
@@ -18,7 +18,7 @@ define([
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
- $('#sbox-iframe').attr('src',
+ document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + window.location.pathname + 'inner.html?' +
requireConfig.urlArgs + '#' + encodeURIComponent(JSON.stringify(req)));
@@ -37,4 +37,4 @@ define([
}).nThen(function (/*waitFor*/) {
SFCommonO.start();
});
-});
\ No newline at end of file
+});
diff --git a/www/common/sframe-common-file.js b/www/common/sframe-common-file.js
index a07ce8e0c..4d0370122 100644
--- a/www/common/sframe-common-file.js
+++ b/www/common/sframe-common-file.js
@@ -212,7 +212,9 @@ define([
queue.next();
};
- var showNamePrompt = true;
+ // Don't show the rename prompt if we don't want to store the file in the drive (avatar)
+ var showNamePrompt = !config.noStore;
+
var promptName = function (file, cb) {
var extIdx = file.name.lastIndexOf('.');
var name = extIdx !== -1 ? file.name.slice(0,extIdx) : file.name;
diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js
index 3e6c7c84c..c9c6c9526 100644
--- a/www/common/sframe-common-outer.js
+++ b/www/common/sframe-common-outer.js
@@ -9,6 +9,7 @@ define([
common.start = function (cfg) {
cfg = cfg || {};
var realtime = !cfg.noRealtime;
+ var network;
var secret;
var hashes;
var CpNfOuter;
@@ -18,7 +19,7 @@ define([
var SFrameChannel;
var sframeChan;
var FilePicker;
- var Messenger;
+ //var Messenger;
var Messaging;
var Notifier;
var Utils = {};
@@ -32,7 +33,6 @@ define([
'/common/cryptget.js',
'/common/sframe-channel.js',
'/filepicker/main.js',
- '/common/common-messenger.js',
'/common/common-messaging.js',
'/common/common-notifier.js',
'/common/common-hash.js',
@@ -41,16 +41,17 @@ define([
'/common/common-constants.js',
'/common/common-feedback.js',
'/common/outer/local-store.js',
+ '/common/outer/network-config.js',
+ '/bower_components/netflux-websocket/netflux-client.js',
], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel,
- _FilePicker, _Messenger, _Messaging, _Notifier, _Hash, _Util, _Realtime,
- _Constants, _Feedback, _LocalStore) {
+ _FilePicker, _Messaging, _Notifier, _Hash, _Util, _Realtime,
+ _Constants, _Feedback, _LocalStore, NetConfig, Netflux) {
CpNfOuter = _CpNfOuter;
Cryptpad = _Cryptpad;
Crypto = _Crypto;
Cryptget = _Cryptget;
SFrameChannel = _SFrameChannel;
FilePicker = _FilePicker;
- Messenger = _Messenger;
Messaging = _Messaging;
Notifier = _Notifier;
Utils.Hash = _Hash;
@@ -84,7 +85,15 @@ define([
SFrameChannel.create($('#sbox-iframe')[0].contentWindow, waitFor(function (sfc) {
sframeChan = sfc;
}), false, { cache: cache, localStore: localStore, language: Cryptpad.getLanguage() });
- Cryptpad.ready(waitFor());
+ Cryptpad.ready(waitFor(), {
+ messenger: cfg.messaging
+ });
+
+ if (!cfg.newNetwork) {
+ Netflux.connect(NetConfig.getWebsocketURL()).then(waitFor(function (nw) {
+ network = nw;
+ }));
+ }
}));
}).nThen(function (waitFor) {
$('#sbox-iframe').focus();
@@ -104,12 +113,23 @@ 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) {
+ var w = waitFor();
+ cfg.getSecrets(Cryptpad, Utils, waitFor(function (err, s) {
+ secret = s;
+ Cryptpad.getShareHashes(secret, function (err, h) {
+ hashes = h;
+ w();
+ });
+ }));
+ } 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; }));
}
- Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; }));
}).nThen(function () {
var readOnly = secret.keys && !secret.keys.editKeyStr;
@@ -117,59 +137,50 @@ define([
var parsed = Utils.Hash.parsePadUrl(window.location.href);
if (!parsed.type) { throw new Error(); }
var defaultTitle = Utils.Hash.getDefaultName(parsed);
- 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); }
- name = n;
+ metaObj = m;
+ }));
+ Cryptpad.isTemplate(window.location.href, waitFor(function (err, t) {
+ if (err) { console.log(err); }
+ 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);
Utils.LocalStore.onLogout(function () {
sframeChan.event('EV_LOGOUT');
@@ -223,7 +234,7 @@ define([
sframeChan.on('Q_SET_PAD_TITLE_IN_DRIVE', function (newTitle, cb) {
currentTitle = newTitle;
setDocumentTitle();
- Cryptpad.renamePad(newTitle, undefined, function (err) {
+ Cryptpad.setPadTitle(newTitle, undefined, undefined, function (err) {
cb(err);
});
});
@@ -241,7 +252,7 @@ define([
cb('ERROR');
return;
}
- Cryptpad.changeDisplayName(newName, true);
+ Cryptpad.changeMetadata();
cb();
});
});
@@ -287,7 +298,6 @@ define([
};
sframeChan.on('Q_GET_FULL_HISTORY', function (data, cb) {
- var network = Cryptpad.getNetwork();
var hkn = network.historyKeeper;
var crypto = Crypto.createEncryptor(secret.keys);
// Get the history messages and send them to the iframe
@@ -445,8 +455,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) {
@@ -494,117 +505,58 @@ define([
}
if (cfg.messaging) {
- var messenger = Messenger.messenger(Cryptpad);
-
sframeChan.on('Q_CONTACTS_GET_FRIEND_LIST', function (data, cb) {
- messenger.getFriendList(function (e, keys) {
- cb({
- error: e,
- data: keys,
- });
- });
+ Cryptpad.messenger.getFriendList(cb);
});
sframeChan.on('Q_CONTACTS_GET_MY_INFO', function (data, cb) {
- messenger.getMyInfo(function (e, info) {
- cb({
- error: e,
- data: info,
- });
- });
+ Cryptpad.messenger.getMyInfo(cb);
});
sframeChan.on('Q_CONTACTS_GET_FRIEND_INFO', function (curvePublic, cb) {
- messenger.getFriendInfo(curvePublic, function (e, info) {
- cb({
- error: e,
- data: info,
- });
- });
+ Cryptpad.messenger.getFriendInfo(curvePublic, cb);
});
sframeChan.on('Q_CONTACTS_REMOVE_FRIEND', function (curvePublic, cb) {
- messenger.removeFriend(curvePublic, function (e, info) {
- cb({
- error: e,
- data: info,
- });
- });
+ Cryptpad.messenger.removeFriend(curvePublic, cb);
});
sframeChan.on('Q_CONTACTS_OPEN_FRIEND_CHANNEL', function (curvePublic, cb) {
- messenger.openFriendChannel(curvePublic, function (e) {
- cb({ error: e, });
- });
+ Cryptpad.messenger.openFriendChannel(curvePublic, cb);
});
sframeChan.on('Q_CONTACTS_GET_STATUS', function (curvePublic, cb) {
- messenger.getStatus(curvePublic, function (e, online) {
- cb({
- error: e,
- data: online,
- });
- });
+ Cryptpad.messenger.getFriendStatus(curvePublic, cb);
});
sframeChan.on('Q_CONTACTS_GET_MORE_HISTORY', function (opt, cb) {
- messenger.getMoreHistory(opt.curvePublic, opt.sig, opt.count, function (e, history) {
- cb({
- error: e,
- data: history,
- });
- });
+ Cryptpad.messenger.getMoreHistory(opt, cb);
});
sframeChan.on('Q_CONTACTS_SEND_MESSAGE', function (opt, cb) {
- messenger.sendMessage(opt.curvePublic, opt.content, function (e) {
- cb({
- error: e,
- });
- });
+ Cryptpad.messenger.sendMessage(opt, cb);
});
sframeChan.on('Q_CONTACTS_SET_CHANNEL_HEAD', function (opt, cb) {
- messenger.setChannelHead(opt.curvePublic, opt.sig, function (e) {
- cb({
- error: e
- });
- });
+ Cryptpad.messenger.setChannelHead(opt, cb);
});
sframeChan.on('Q_CONTACTS_CLEAR_OWNED_CHANNEL', function (channel, cb) {
- messenger.clearOwnedChannel(channel, function (e) {
- cb({
- error: e,
- });
- });
+ Cryptpad.clearOwnedChannel(channel, cb);
});
- messenger.on('message', function (message) {
- sframeChan.event('EV_CONTACTS_MESSAGE', message);
+ Cryptpad.messenger.onMessageEvent.reg(function (data) {
+ sframeChan.event('EV_CONTACTS_MESSAGE', data);
});
- messenger.on('join', function (curvePublic, channel) {
- sframeChan.event('EV_CONTACTS_JOIN', {
- curvePublic: curvePublic,
- channel: channel,
- });
+ Cryptpad.messenger.onJoinEvent.reg(function (data) {
+ sframeChan.event('EV_CONTACTS_JOIN', data);
});
- messenger.on('leave', function (curvePublic, channel) {
- sframeChan.event('EV_CONTACTS_LEAVE', {
- curvePublic: curvePublic,
- channel: channel,
- });
+ Cryptpad.messenger.onLeaveEvent.reg(function (data) {
+ sframeChan.event('EV_CONTACTS_LEAVE', data);
});
- messenger.on('update', function (info, curvePublic) {
- sframeChan.event('EV_CONTACTS_UPDATE', {
- curvePublic: curvePublic,
- info: info,
- });
+ Cryptpad.messenger.onUpdateEvent.reg(function (data) {
+ sframeChan.event('EV_CONTACTS_UPDATE', data);
});
- messenger.on('friend', function (curvePublic) {
- sframeChan.event('EV_CONTACTS_FRIEND', {
- curvePublic: curvePublic,
- });
+ Cryptpad.messenger.onFriendEvent.reg(function (data) {
+ sframeChan.event('EV_CONTACTS_FRIEND', data);
});
- messenger.on('unfriend', function (curvePublic) {
- sframeChan.event('EV_CONTACTS_UNFRIEND', {
- curvePublic: curvePublic,
- });
+ Cryptpad.messenger.onUnfriendEvent.reg(function (data) {
+ sframeChan.event('EV_CONTACTS_UNFRIEND', data);
});
}
@@ -629,7 +581,7 @@ define([
CpNfOuter.start({
sframeChan: sframeChan,
channel: secret.channel,
- network: cfg.newNetwork || Cryptpad.getNetwork(),
+ network: cfg.newNetwork || network,
validateKey: secret.keys.validateKey || undefined,
readOnly: readOnly,
crypto: Crypto.createEncryptor(secret.keys),
diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js
index f50ff71da..01ae4c38c 100644
--- a/www/common/sframe-common.js
+++ b/www/common/sframe-common.js
@@ -328,7 +328,7 @@ define([
nThen(function (waitFor) {
SFrameChannel.create(window.parent, waitFor(function (sfc) { ctx.sframeChan = sfc; }), true);
// CpNfInner.start() should be here....
- }).nThen(function () {
+ }).nThen(function (waitFor) {
localForage.clear();
ctx.metadataMgr = MetadataMgr.create(ctx.sframeChan);
@@ -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);
});
@@ -393,6 +389,8 @@ define([
} catch (e) { Feedback.init(false); }
});
+ ctx.metadataMgr.onReady(waitFor());
+ }).nThen(function () {
ctx.sframeChan.ready();
cb(funcs);
});
diff --git a/www/common/toolbar3.js b/www/common/toolbar3.js
index 1227a1dbc..5bdd8486d 100644
--- a/www/common/toolbar3.js
+++ b/www/common/toolbar3.js
@@ -767,7 +767,7 @@ define([
var origin = privateData.origin;
var pathname = privateData.pathname;
var href = inDrive.test(pathname) ? origin+'/index.html' : origin+'/drive/';
- var buttonTitle = inDrive ? Messages.header_homeTitle : Messages.header_logoTitle;
+ var buttonTitle = inDrive.test(pathname) ? Messages.header_homeTitle : Messages.header_logoTitle;
var $aTag = $('', {
href: href,
diff --git a/www/common/userObject.js b/www/common/userObject.js
index 58e45e8ee..344677ea8 100644
--- a/www/common/userObject.js
+++ b/www/common/userObject.js
@@ -1,12 +1,11 @@
define([
- 'jquery',
'/customize/application_config.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/common-constants.js',
'/customize/messages.js'
-], function ($, AppConfig, Util, Hash, Realtime, Constants, Messages) {
+], function (AppConfig, Util, Hash, Realtime, Constants, Messages) {
var module = {};
var ROOT = module.ROOT = "root";
@@ -21,13 +20,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;
@@ -101,7 +100,7 @@ define([
};
for (var f in element) {
if (trashRoot) {
- if ($.isArray(element[f])) {
+ if (Array.isArray(element[f])) {
element[f].forEach(addSubfolder);
}
} else {
@@ -119,7 +118,7 @@ define([
};
for (var f in element) {
if (trashRoot) {
- if ($.isArray(element[f])) {
+ if (Array.isArray(element[f])) {
element[f].forEach(addFile);
}
} else {
@@ -148,14 +147,14 @@ define([
return data.filename || data.title || NEW_FILE_NAME;
};
exp.getPadAttribute = function (href, attr, cb) {
- cb = cb || $.noop;
+ cb = cb || function () {};
var id = exp.getIdFromHref(href);
if (!id) { return void cb(null, undefined); }
var data = getFileData(id);
cb(null, clone(data[attr]));
};
exp.setPadAttribute = function (href, attr, value, cb) {
- cb = cb || $.noop;
+ cb = cb || function () {};
var id = exp.getIdFromHref(href);
if (!id) { return void cb("E_INVAL_HREF"); }
if (!attr || !attr.trim()) { return void cb("E_INVAL_ATTR"); }
@@ -167,7 +166,7 @@ define([
// PATHS
var comparePath = exp.comparePath = function (a, b) {
- if (!a || !b || !$.isArray(a) || !$.isArray(b)) { return false; }
+ if (!a || !b || !Array.isArray(a) || !Array.isArray(b)) { return false; }
if (a.length !== b.length) { return false; }
var result = true;
var i = a.length - 1;
@@ -265,7 +264,7 @@ define([
}
};
for (var e in root) {
- if (!$.isArray(root[e])) {
+ if (!Array.isArray(root[e])) {
error("Trash contains a non-array element");
return;
}
@@ -487,8 +486,6 @@ define([
// FILES DATA
exp.pushData = function (data, cb) {
- // TODO: can only be called from outside atm
- if (!Cryptpad) { return; }
if (typeof cb !== "function") { cb = function () {}; }
var todo = function () {
var id = Util.createRandomInteger();
@@ -498,8 +495,9 @@ define([
if (!loggedIn || !AppConfig.enablePinning || config.testMode) {
return void todo();
}
- Cryptpad.pinPads([Hash.hrefToHexChannelId(data.href)], function (e) {
- if (e) { return void cb(e); }
+ if (!pinPads) { return; }
+ pinPads([Hash.hrefToHexChannelId(data.href)], function (obj) {
+ if (obj && obj.error) { return void cb(obj.error); }
todo();
});
};
@@ -968,7 +966,7 @@ define([
var addToClean = function (obj, idx, el) {
if (typeof(obj) !== "object") { toClean.push(idx); return; }
if (!isFile(obj.element, true) && !isFolder(obj.element)) { toClean.push(idx); return; }
- if (!$.isArray(obj.path)) { toClean.push(idx); return; }
+ if (!Array.isArray(obj.path)) { toClean.push(idx); return; }
if (typeof obj.element === "string") {
// We have an old file (href) which is not in filesData: add it
var id = Util.createRandomInteger();
diff --git a/www/common/wire.js b/www/common/wire.js
new file mode 100644
index 000000000..c4b59cfae
--- /dev/null
+++ b/www/common/wire.js
@@ -0,0 +1,94 @@
+define([
+
+], function () {
+ var Wire = {};
+
+ /* MISSION: write a generic RPC framework
+
+Requirements
+
+* some transmission methods can be interrupted
+ * handle disconnects and reconnects
+* handle callbacks
+* configurable timeout
+* Service should expose 'addClient' method
+ * and handle broadcast
+
+
+*
+
+ */
+
+ var uid = function () {
+ return Number(Math.floor(Math.random () *
+ Number.MAX_SAFE_INTEGER)).toString(32);
+ };
+
+/*
+opt = {
+ send: function () {
+
+ },
+ receive: function () {
+
+ },
+ constructor: function (cb) {
+ cb(void 0 , {
+ send: function (content, cb) {
+
+ },
+ receive: function () {
+
+ }
+ });
+ },
+};
+*/
+
+ Wire.create = function (opt, cb) {
+ var ctx = {};
+ var pending = ctx.pending = {};
+ ctx.connected = false;
+
+ var rpc = {};
+
+ opt.constructor(function (e, service) {
+ if (e) { return setTimeout(function () { cb(e); }); }
+
+ rpc.send = function (type, data, cb) {
+ var txid = uid();
+ if (typeof(cb) !== 'function') {
+ throw new Error('expected callback');
+ }
+
+ ctx.pending[txid] = function (err, response) {
+ cb(err, response);
+ };
+
+ service.send(JSON.stringify({
+ txid: txid,
+ message: {
+ command: type,
+ content: data,
+ },
+ }));
+ };
+
+ service.receive(function (raw) {
+ try {
+ var data = JSON.parse(raw);
+ var txid = data.txid;
+ if (!txid) { throw new Error('NO_TXID'); }
+ var cb = pending[txid];
+ if (data.error) { return void cb(data.error); }
+ cb(void 0, data.content);
+ } catch (e) { console.error("UNHANDLED_MESSAGE", raw); }
+ });
+
+ cb(void 0, rpc);
+ });
+ };
+
+
+ return Wire;
+});
diff --git a/www/contacts/main.js b/www/contacts/main.js
index f99e7d472..38d6c5e71 100644
--- a/www/contacts/main.js
+++ b/www/contacts/main.js
@@ -2,15 +2,15 @@
define([
'/bower_components/nthen/index.js',
'/api/config',
- 'jquery',
+ '/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js'
-], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) {
+], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
// Loaded in load #2
nThen(function (waitFor) {
- $(waitFor());
+ DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
@@ -19,7 +19,7 @@ define([
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
- $('#sbox-iframe').attr('src',
+ document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/contacts/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req)));
diff --git a/www/debug/inner.js b/www/debug/inner.js
index f45670b41..bb4e1ecf2 100644
--- a/www/debug/inner.js
+++ b/www/debug/inner.js
@@ -1,10 +1,8 @@
define([
'jquery',
'/bower_components/chainpad-crypto/crypto.js',
- '/bower_components/textpatcher/TextPatcher.js',
'/common/toolbar3.js',
'json.sortify',
- '/bower_components/chainpad-json-validator/json-ot.js',
'/common/common-util.js',
'/bower_components/nthen/index.js',
'/common/sframe-common.js',
@@ -22,10 +20,8 @@ define([
], function (
$,
Crypto,
- TextPatcher,
Toolbar,
JSONSortify,
- JsonOT,
Util,
nThen,
SFCommon,
@@ -61,7 +57,6 @@ define([
var config = APP.config = {
readOnly: readOnly,
- transformFunction: JsonOT.validate,
// cryptpad debug logging (default is 1)
// logLevel: 0,
validateContent: function (content) {
@@ -123,11 +118,7 @@ define([
config.onReady = function (info) {
if (APP.realtime !== info.realtime) {
- var realtime = APP.realtime = info.realtime;
- APP.patchText = TextPatcher.create({
- realtime: realtime,
- //logging: true
- });
+ APP.realtime = info.realtime;
}
var userDoc = APP.realtime.getUserDoc();
diff --git a/www/drive/main.js b/www/drive/main.js
index 1e3ff3589..de668fc4d 100644
--- a/www/drive/main.js
+++ b/www/drive/main.js
@@ -2,17 +2,15 @@
define([
'/bower_components/nthen/index.js',
'/api/config',
- 'jquery',
+ '/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js',
- '/common/outer/network-config.js',
- '/bower_components/netflux-websocket/netflux-client.js',
-], function (nThen, ApiConfig, $, RequireConfig, SFCommonO, NetConfig, Netflux) {
+], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
// Loaded in load #2
nThen(function (waitFor) {
- $(waitFor());
+ DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
@@ -21,7 +19,7 @@ define([
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
- $('#sbox-iframe').attr('src',
+ document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/drive/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req)));
@@ -38,10 +36,10 @@ define([
};
window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) {
- var getSecrets = function (Cryptpad, Utils) {
+ var getSecrets = function (Cryptpad, Utils, cb) {
var hash = window.location.hash.slice(1) || Utils.LocalStore.getUserHash() ||
Utils.LocalStore.getFSHash();
- return Utils.Hash.getSecrets('drive', hash);
+ cb(null, Utils.Hash.getSecrets('drive', hash));
};
var addRpc = function (sframeChan, Cryptpad, Utils) {
sframeChan.on('EV_BURN_ANON_DRIVE', function () {
@@ -51,13 +49,13 @@ define([
window.location.reload();
});
};
- Netflux.connect(NetConfig.getWebsocketURL()).then(function (network) {
+ //Netflux.connect(NetConfig.getWebsocketURL()).then(function (network) {
SFCommonO.start({
getSecrets: getSecrets,
- newNetwork: network,
+ //newNetwork: network,
noHash: true,
addRpc: addRpc
});
- }, function (err) { console.error(err); });
+ //}, function (err) { console.error(err); });
});
});
diff --git a/www/drive/tests.js b/www/drive/tests.js
index 23ed2172f..7132fd8a7 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
@@ -325,7 +325,12 @@ define([
var fo = FO.init(files, config);
fo.fixFiles();
- var data = Cryptpad.makePad(href5, 'Title5');
+ var data = {
+ href: href5,
+ title: 'Title5',
+ atime: +new Date(),
+ ctime: +new Date()
+ };
var res;
var id5;
// pushData is synchronous in test mode (no pinning)
diff --git a/www/file/inner.js b/www/file/inner.js
index 09a302b0a..0bc7a2000 100644
--- a/www/file/inner.js
+++ b/www/file/inner.js
@@ -120,7 +120,6 @@ define([
decrypted.callback();
}
- console.log(decrypted);
$dlview.show();
$dlform.hide();
var $dlButton = $dlview.find('media-tag button');
@@ -174,7 +173,6 @@ define([
var progress = e.originalEvent;
var p = progress.percent +'%';
$progress.width(p);
- console.log(progress.percent);
});
/**
diff --git a/www/file/main.js b/www/file/main.js
index 0af6558ef..e59957299 100644
--- a/www/file/main.js
+++ b/www/file/main.js
@@ -2,15 +2,15 @@
define([
'/bower_components/nthen/index.js',
'/api/config',
- 'jquery',
+ '/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js'
-], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) {
+], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
// Loaded in load #2
nThen(function (waitFor) {
- $(waitFor());
+ DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
@@ -19,7 +19,7 @@ define([
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
- $('#sbox-iframe').attr('src',
+ document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/file/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req)));
diff --git a/www/filepicker/main.js b/www/filepicker/main.js
index d628b401c..8b7ab2b60 100644
--- a/www/filepicker/main.js
+++ b/www/filepicker/main.js
@@ -46,41 +46,30 @@ define([
sframeChan = sfc;
}));
}).nThen(function () {
- 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);
config.addCommonRpc(sframeChan);
diff --git a/www/poll/main.js b/www/poll/main.js
index 04c9b8f2f..737038ead 100644
--- a/www/poll/main.js
+++ b/www/poll/main.js
@@ -2,15 +2,15 @@
define([
'/bower_components/nthen/index.js',
'/api/config',
- 'jquery',
+ '/common/dom-ready.js',
'/common/requireconfig.js',
'/common/sframe-common-outer.js',
-], function (nThen, ApiConfig, $, RequireConfig, SFCommonO) {
+], function (nThen, ApiConfig, DomReady, RequireConfig, SFCommonO) {
var requireConfig = RequireConfig();
// Loaded in load #2
nThen(function (waitFor) {
- $(waitFor());
+ DomReady.onReady(waitFor());
}).nThen(function (waitFor) {
var req = {
cfg: requireConfig,
@@ -19,7 +19,7 @@ define([
};
window.rc = requireConfig;
window.apiconf = ApiConfig;
- $('#sbox-iframe').attr('src',
+ document.getElementById('sbox-iframe').setAttribute('src',
ApiConfig.httpSafeOrigin + '/poll/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req)));
diff --git a/www/profile/app-profile.less b/www/profile/app-profile.less
index f7cd747d3..21e659a03 100644
--- a/www/profile/app-profile.less
+++ b/www/profile/app-profile.less
@@ -73,6 +73,9 @@
margin: 5px;
}
}
+ .cp-app-profile-resizer {
+ text-align: center;
+ }
#cp-app-profile-displayname, #cp-app-profile-link {
width: 100%;
height: 40px;
diff --git a/www/profile/inner.js b/www/profile/inner.js
index 9b48e2ca2..6cab88eae 100644
--- a/www/profile/inner.js
+++ b/www/profile/inner.js
@@ -20,6 +20,8 @@ define([
'css!/bower_components/bootstrap/dist/css/bootstrap.min.css',
'less!/bower_components/components-font-awesome/css/font-awesome.min.css',
'less!/customize/src/less2/main.less',
+ '/bower_components/croppie/croppie.min.js',
+ 'css!/bower_components/croppie/croppie.css',
], function (
$,
Crypto,
@@ -253,12 +255,44 @@ define([
createEditableInput($block, LINK_ID, placeholder, getValue, setValue);
};
+ var AVATAR_SIZE_LIMIT = 0.5;
var allowedMediaTypes = [
'image/png',
'image/jpeg',
'image/jpg',
'image/gif',
];
+ var transformAvatar = function (file, cb) {
+ if (file.type === 'image/gif') { return void cb(file); }
+ var $croppie = $('