Merge branch 'staging' of github.com:xwiki-labs/cryptpad into staging

pull/1/head
ansuz 7 years ago
commit e85e2dcabd

@ -12,7 +12,7 @@ var map = {
var messages = {};
var LS_LANG = "CRYPTPAD_LANG";
var getStoredLanguage = function () { return localStorage.getItem(LS_LANG); };
var getStoredLanguage = function () { return localStorage && localStorage.getItem(LS_LANG); };
var getBrowserLanguage = function () { return navigator.language || navigator.userLanguage || ''; };
var getLanguage = messages._getLanguage = function () {
if (window.cryptpadLanguage) { return window.cryptpadLanguage; }
@ -24,19 +24,17 @@ var getLanguage = messages._getLanguage = function () {
};
var language = getLanguage();
var req = ['jquery', '/customize/translations/messages.js'];
var req = ['/common/common-util.js', '/customize/translations/messages.js'];
if (language && map[language]) { req.push('/customize/translations/messages.' + language + '.js'); }
define(req, function($, Default, Language) {
define(req, function(Util, Default, Language) {
map.en = 'English';
var defaultLanguage = 'en';
if (!Language || language === defaultLanguage || !map[language]) {
messages = $.extend(true, messages, Default);
}
else {
Util.extend(messages, Default);
if (Language && language !== defaultLanguage) {
// Add the translated keys to the returned object
messages = $.extend(true, messages, Default, Language);
Util.extend(messages, Language);
}
messages._languages = map;

@ -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,

@ -10,5 +10,6 @@ define(function () {
displayNameKey: 'cryptpad.username',
oldStorageKey: 'CryptPad_RECENTPADS',
storageKey: 'filesData',
tokenKey: 'loginToken',
};
});

@ -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);

@ -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;

@ -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]
});
};

@ -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;
});

@ -571,12 +571,7 @@ define([
// getPinnedUsage updates common.account.usage, and other values
// so we can just use those and only check for errors
var $container = $('<span>', {'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 +640,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);

@ -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));

@ -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);
});

File diff suppressed because it is too large Load Diff

@ -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(); }
};
}
};
});

@ -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;
});

@ -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;

@ -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);
}
});
};

@ -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;
});

@ -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;
});

@ -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);

@ -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');
});
}

@ -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');
});
}

@ -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();
});
});
});

@ -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); }
metaObj = m;
}));
Cryptpad.isTemplate(window.location.href, waitFor(function (err, t) {
if (err) { console.log(err); }
name = n;
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),

@ -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);
});

@ -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();

@ -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)));

@ -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();

@ -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); });
});
});

@ -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)

@ -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);
});
/**

@ -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)));

@ -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);

@ -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)));

@ -395,15 +395,6 @@ define([
var onReady = function () {
APP.$container.find('#'+CREATE_ID).remove();
/*var obj = APP.lm && APP.lm.proxy;
if (!APP.readOnly) {
var pubKeys = Cryptpad.getPublicKeys();
if (pubKeys && pubKeys.curve) {
obj.curveKey = pubKeys.curve;
obj.edKey = pubKeys.ed;
}
}*/
if (!APP.initialized) {
var $header = $('<div>', {id: HEADER_ID}).appendTo(APP.$rightside);
addAvatar($header);

@ -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 + '/profile/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req)));
@ -36,34 +36,41 @@ define([
};
window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) {
var getSecrets = function (Cryptpad, Utils) {
var getSecrets = function (Cryptpad, Utils, cb) {
var Hash = Utils.Hash;
// 1st case: visiting someone else's profile with hash in the URL
if (window.location.hash) {
return Hash.getSecrets('profile', window.location.hash.slice(1));
return void cb(null, Hash.getSecrets('profile', window.location.hash.slice(1)));
}
// 2nd case: visiting our own existing profile
var obj = Cryptpad.getProxy();
if (obj.profile && obj.profile.view && obj.profile.edit) {
return Hash.getSecrets('profile', obj.profile.edit);
}
// 3rd case: profile creation (create a new random hash, store it later if needed)
if (!Utils.LocalStore.isLoggedIn()) { return; }
var hash = Hash.createRandomHash();
var secret = Hash.getSecrets('profile', hash);
Cryptpad.pinPads([secret.channel], function (e) {
if (e) {
if (e === 'E_OVER_LIMIT') {
// TODO
}
return;
//return void UI.log(Messages._getKey('profile_error', [e])) // TODO
var editHash;
nThen(function (waitFor) {
// 2nd case: visiting our own existing profile
Cryptpad.getProfileEditUrl(waitFor(function (hash) {
editHash = hash;
}));
}).nThen(function () {
if (editHash) {
return void cb(null, Hash.getSecrets('profile', editHash));
}
obj.profile = {};
obj.profile.edit = Utils.Hash.getEditHashFromKeys(secret.channel, secret.keys);
obj.profile.view = Utils.Hash.getViewHashFromKeys(secret.channel, secret.keys);
// 3rd case: profile creation (create a new random hash, store it later if needed)
if (!Utils.LocalStore.isLoggedIn()) { return void cb(); }
var hash = Hash.createRandomHash();
var secret = Hash.getSecrets('profile', hash);
Cryptpad.pinPads([secret.channel], function (e) {
if (e) {
if (e === 'E_OVER_LIMIT') {
// TODO
}
return;
//return void UI.log(Messages._getKey('profile_error', [e])) // TODO
}
var profile = {};
profile.edit = Utils.Hash.getEditHashFromKeys(secret.channel, secret.keys);
profile.view = Utils.Hash.getViewHashFromKeys(secret.channel, secret.keys);
Cryptpad.setNewProfile(profile);
});
cb(null, secret);
});
return secret;
};
var addRpc = function (sframeChan, Cryptpad, Utils) {
// Adding a new avatar from the profile: pin it and store it in the object
@ -71,18 +78,14 @@ define([
var chanId = Utils.Hash.hrefToHexChannelId(data);
Cryptpad.pinPads([chanId], function (e) {
if (e) { return void cb(e); }
Cryptpad.getProxy().profile.avatar = data;
Utils.Realtime.whenRealtimeSyncs(Cryptpad.getRealtime(), function () {
cb();
});
Cryptpad.setAvatar(data, cb);
});
});
// Removing the avatar from the profile: unpin it
sframeChan.on('Q_PROFILE_AVATAR_REMOVE', function (data, cb) {
var chanId = Utils.Hash.hrefToHexChannelId(data);
Cryptpad.unpinPads([chanId], function (e) {
delete Cryptpad.getProxy().profile.avatar;
cb(e);
Cryptpad.unpinPads([chanId], function () {
Cryptpad.setAvatar(undefined, cb);
});
});
};

@ -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 + '/settings/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req)));
@ -43,7 +43,7 @@ define([
});
});
sframeChan.on('Q_SETTINGS_DRIVE_GET', function (d, cb) {
cb(Cryptpad.getProxy());
Cryptpad.getUserObject(cb);
});
sframeChan.on('Q_SETTINGS_DRIVE_SET', function (data, cb) {
var sjson = JSON.stringify(data);
@ -57,26 +57,13 @@ define([
});
});
sframeChan.on('Q_SETTINGS_DRIVE_RESET', function (data, cb) {
var proxy = Cryptpad.getProxy();
var realtime = Cryptpad.getRealtime();
proxy.drive = Cryptpad.getStore().getEmptyObject();
Utils.Realtime.whenRealtimeSyncs(realtime, cb);
Cryptpad.resetDrive(cb);
});
sframeChan.on('Q_SETTINGS_LOGOUT', function (data, cb) {
var proxy = Cryptpad.getProxy();
var realtime = Cryptpad.getRealtime();
var token = Math.floor(Math.random()*Number.MAX_SAFE_INTEGER);
localStorage.setItem('loginToken', token);
proxy.loginToken = token;
Utils.Realtime.whenRealtimeSyncs(realtime, cb);
Cryptpad.logoutFromAll(cb);
});
sframeChan.on('Q_SETTINGS_IMPORT_LOCAL', function (data, cb) {
var proxyData = Cryptpad.getStore().getProxy();
require([
'/common/mergeDrive.js',
], function (Merge) {
Merge.anonDriveIntoUser(proxyData, Utils.LocalStore.getFSHash(), cb);
});
Cryptpad.mergeAnonDrive(cb);
});
};
SFCommonO.start({

@ -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 + '/todo/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req)));
@ -36,12 +36,12 @@ define([
};
window.addEventListener('message', onMsg);
}).nThen(function (/*waitFor*/) {
var getSecrets = function (Cryptpad, Utils) {
var proxy = Cryptpad.getProxy();
var hash = proxy.todo || Utils.Hash.createRandomHash();
if (!proxy.todo) { proxy.todo = hash; }
return Utils.Hash.getSecrets('todo', hash);
var getSecrets = function (Cryptpad, Utils, cb) {
Cryptpad.getTodoHash(function (hash) {
var nHash = hash || Utils.Hash.createRandomHash();
if (!hash) { Cryptpad.setTodoHash(nHash); }
cb(null, Utils.Hash.getSecrets('todo', hash));
});
};
SFCommonO.start({
getSecrets: getSecrets,

@ -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 + '/whiteboard/inner.html?' + requireConfig.urlArgs +
'#' + encodeURIComponent(JSON.stringify(req)));

Loading…
Cancel
Save