Merge branch 'staging' into oo2

This commit is contained in:
yflory
2018-09-03 10:39:34 +02:00
197 changed files with 20203 additions and 8239 deletions

View File

@@ -4,9 +4,11 @@ const define = (x:any, y:any) => {};
const require = define;
*/
define([
'/api/config'
], function (Config) { /*::});module.exports = (function() {
'/api/config',
'/bower_components/nthen/index.js'
], function (Config, nThen) { /*::});module.exports = (function() {
const Config = (undefined:any);
const nThen = (undefined:any);
*/
var module = { exports: {} };
@@ -49,6 +51,7 @@ define([
if (ua[0].indexOf(':') === -1 && ua[0].indexOf('/') && parent) {
ua[0] = parent.replace(/\/[^\/]*$/, '/') + ua[0];
}
ua[0] = ua[0].replace(/^\/\.\.\//, '/');
var out = ua.join('#');
//console.log(url + " --> " + out);
return out;
@@ -91,17 +94,36 @@ define([
};
var lessEngine;
var tempCache = { key: Math.random() };
var getLessEngine = function (cb) {
if (lessEngine) {
cb(lessEngine);
} else {
require(['/bower_components/less/dist/less.min.js'], function (Less) {
if (lessEngine) { return void cb(lessEngine); }
lessEngine = Less;
Less.functions.functionRegistry.add('LessLoader_currentFile', function () {
return new Less.tree.UnicodeDescriptor('"' +
fixURL(this.currentFileInfo.filename) + '"');
});
var doXHR = lessEngine.FileManager.prototype.doXHR;
lessEngine.FileManager.prototype.doXHR = function (url, type, callback, errback) {
url = fixURL(url);
//console.log("xhr: " + url);
return doXHR(url, type, callback, errback);
var cached = tempCache[url];
if (cached && cached.res) {
var res = cached.res;
return void setTimeout(function () { callback(res[0], res[1]); });
}
if (cached) { return void cached.queue.push(callback); }
cached = tempCache[url] = { queue: [ callback ], res: undefined };
return doXHR(url, type, function (text, lastModified) {
cached.res = [ text, lastModified ];
var queue = cached.queue;
cached.queue = [];
queue.forEach(function (f) {
setTimeout(function () { f(text, lastModified); });
});
}, errback);
};
cb(lessEngine);
});
@@ -117,19 +139,38 @@ define([
});
};
module.exports.load = function (url /*:string*/, cb /*:()=>void*/) {
cacheGet(url, function (css) {
if (css) {
inject(css, url);
return void cb();
var loadSubmodulesAndInject = function (css, url, cb, stack) {
inject(css, url);
nThen(function (w) {
css.replace(/\-\-LessLoader_require\:\s*"([^"]*)"\s*;/g, function (all, u) {
u = u.replace(/\?.*$/, '');
module.exports.load(u, w(), stack);
return '';
});
}).nThen(function () { cb(); });
};
module.exports.load = function (url /*:string*/, cb /*:()=>void*/, stack /*:?Array<string>*/) {
var btime = stack ? null : +new Date();
stack = stack || [];
if (stack.indexOf(url) > -1) { return void cb(); }
var timeout = setTimeout(function () { console.log('failed', url); }, 10000);
var done = function () {
clearTimeout(timeout);
if (btime) {
console.log("Compiling [" + url + "] took " + (+new Date() - btime) + "ms");
}
cb();
};
stack.push(url);
cacheGet(url, function (css) {
if (css) { return void loadSubmodulesAndInject(css, url, done, stack); }
console.log('CACHE MISS ' + url);
((/\.less([\?\#].*)?$/.test(url)) ? loadLess : loadCSS)(url, function (err, css) {
if (!css) { return void console.error(err); }
var output = fixAllURLs(css, url);
cachePut(url, output);
inject(output, url);
cb();
loadSubmodulesAndInject(output, url, done, stack);
});
});
};

View File

@@ -9,9 +9,9 @@ define(function() {
/* Select the buttons displayed on the main page to create new collaborative sessions
* Existing types : pad, code, poll, slide
*/
config.availablePadTypes = ['drive', 'pad', 'code', 'slide', 'poll', 'whiteboard',
config.availablePadTypes = ['drive', 'pad', 'code', 'slide', 'poll', 'kanban', 'whiteboard',
'oodoc', 'ooslide', 'oocell', 'file', 'todo', 'contacts'];
config.registeredOnlyTypes = ['file', 'contacts'];
config.registeredOnlyTypes = ['file', 'contacts', 'oodoc', 'ooslide', 'oocell'];
/* Cryptpad apps use a common API to display notifications to users
* by default, notifications are hidden after 5 seconds
@@ -85,6 +85,8 @@ define(function() {
oodoc: 'fa-file-word-o',
ooslide: 'fa-file-powerpoint-o',
oocell: 'fa-file-excel-o',
kanban: 'fa-columns',
drive: 'fa-hdd-o',
};
// Ability to create owned pads and expiring pads through a new pad creation screen.
@@ -121,5 +123,18 @@ define(function() {
// You can use config.afterLogin to import these values in the users' drive.
//config.disableProfile = true;
// Disable the use of webworkers and sharedworkers in CryptPad.
// Workers allow us to run the websockets connection and open the user drive in a separate thread.
// SharedWorkers allow us to load only one websocket and one user drive for all the browser tabs,
// making it much faster to open new tabs.
// Warning: This is an experimental feature. It will be enabled by default once we're sure it's stable.
config.disableWorkers = true;
// Shared folder are in a beta-test state. They are likely to disappear from a user's drive
// spontaneously, resulting in the deletion of the entire folder's content.
// We highly recommend to keep them disabled until they are stable enough to be enabled
// by default by the CryptPad developers.
config.disableSharedFolders = true;
return config;
});

View File

@@ -34,7 +34,7 @@ define([
url: '/common/feedback.html?NO_LOCALSTORAGE=' + (+new Date()),
});
});
window.alert("CryptPad needs localStorage to work, try a different browser");
window.alert("CryptPad needs localStorage to work. Try changing your cookie permissions, or using a different browser");
};
window.onerror = function (e) {

View File

@@ -3,6 +3,7 @@ define(function () {
// localStorage
userHashKey: 'User_hash',
userNameKey: 'User_name',
blockHashKey: 'Block_hash',
fileHashKey: 'FS_hash',
// sessionStorage
newPadPathKey: "newPadPath",
@@ -11,6 +12,7 @@ define(function () {
oldStorageKey: 'CryptPad_RECENTPADS',
storageKey: 'filesData',
tokenKey: 'loginToken',
displayPadCreationScreen: 'displayPadCreationScreen'
displayPadCreationScreen: 'displayPadCreationScreen',
deprecatedKey: 'deprecated'
};
});

View File

@@ -11,6 +11,7 @@ define([
var uint8ArrayToHex = Util.uint8ArrayToHex;
var hexToBase64 = Util.hexToBase64;
var base64ToHex = Util.base64ToHex;
Hash.encodeBase64 = Nacl.util.encodeBase64;
// This implementation must match that on the server
// it's used for a checksum
@@ -19,22 +20,53 @@ define([
.decodeUTF8(JSON.stringify(list))));
};
var getEditHashFromKeys = Hash.getEditHashFromKeys = function (chanKey, keys) {
if (typeof keys === 'string') {
return chanKey + keys;
var getEditHashFromKeys = Hash.getEditHashFromKeys = function (secret) {
var version = secret.version;
var data = secret.keys;
if (version === 0) {
return secret.channel + secret.key;
}
if (!keys.editKeyStr) { return; }
return '/1/edit/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.editKeyStr)+'/';
};
var getViewHashFromKeys = Hash.getViewHashFromKeys = function (chanKey, keys) {
if (typeof keys === 'string') {
return;
if (version === 1) {
if (!data.editKeyStr) { return; }
return '/1/edit/' + hexToBase64(secret.channel) +
'/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/';
}
if (version === 2) {
if (!data.editKeyStr) { return; }
var pass = secret.password ? 'p/' : '';
return '/2/' + secret.type + '/edit/' + Crypto.b64RemoveSlashes(data.editKeyStr) + '/' + pass;
}
return '/1/view/' + hexToBase64(chanKey) + '/'+Crypto.b64RemoveSlashes(keys.viewKeyStr)+'/';
};
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (fileKey, cryptKey) {
return '/1/' + hexToBase64(fileKey) + '/' + Crypto.b64RemoveSlashes(cryptKey) + '/';
var getViewHashFromKeys = Hash.getViewHashFromKeys = function (secret) {
var version = secret.version;
var data = secret.keys;
if (version === 0) { return; }
if (version === 1) {
if (!data.viewKeyStr) { return; }
return '/1/view/' + hexToBase64(secret.channel) +
'/'+Crypto.b64RemoveSlashes(data.viewKeyStr)+'/';
}
if (version === 2) {
if (!data.viewKeyStr) { return; }
var pass = secret.password ? 'p/' : '';
return '/2/' + secret.type + '/view/' + Crypto.b64RemoveSlashes(data.viewKeyStr) + '/' + pass;
}
};
var getFileHashFromKeys = Hash.getFileHashFromKeys = function (secret) {
var version = secret.version;
var data = secret.keys;
if (version === 0) { return; }
if (version === 1) {
return '/1/' + hexToBase64(secret.channel) + '/' +
Crypto.b64RemoveSlashes(data.fileKeyStr) + '/';
}
if (version === 2) {
if (!data.fileKeyStr) { return; }
var pass = secret.password ? 'p/' : '';
return '/2/' + secret.type + '/' + Crypto.b64RemoveSlashes(data.fileKeyStr) + '/' + pass;
}
};
Hash.getUserHrefFromKeys = function (origin, username, pubkey) {
return origin + '/user/#/1/' + username + '/' + pubkey.replace(/\//g, '-');
};
@@ -43,6 +75,34 @@ define([
return s.replace(/\/+/g, '/');
};
Hash.createChannelId = function () {
var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16));
if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
throw new Error('channel ids must consist of 32 hex characters');
}
return id;
};
Hash.createRandomHash = function (type, password) {
var cryptor;
if (type === 'file') {
cryptor = Crypto.createFileCryptor2(void 0, password);
return getFileHashFromKeys({
password: Boolean(password),
version: 2,
type: type,
keys: cryptor
});
}
cryptor = Crypto.createEditCryptor2(void 0, void 0, password);
return getEditHashFromKeys({
password: Boolean(password),
version: 2,
type: type,
keys: cryptor
});
};
/*
Version 0
/pad/#67b8385b07352be53e40746d2be6ccd7XAYSuJYYqa9NfmInyHci7LNy
@@ -52,29 +112,60 @@ Version 1
var parseTypeHash = Hash.parseTypeHash = function (type, hash) {
if (!hash) { return; }
var options;
var parsed = {};
var hashArr = fixDuplicateSlashes(hash).split('/');
if (['media', 'file', 'user', 'invite'].indexOf(type) === -1) {
parsed.type = 'pad';
if (hash.slice(0,1) !== '/' && hash.length >= 56) {
parsed.getHash = function () { return hash; };
if (hash.slice(0,1) !== '/' && hash.length >= 56) { // Version 0
// Old hash
parsed.channel = hash.slice(0, 32);
parsed.key = hash.slice(32, 56);
parsed.version = 0;
return parsed;
}
if (hashArr[1] && hashArr[1] === '1') {
if (hashArr[1] && hashArr[1] === '1') { // Version 1
parsed.version = 1;
parsed.mode = hashArr[2];
parsed.channel = hashArr[3];
parsed.key = hashArr[4].replace(/-/g, '/');
var options = hashArr.slice(5);
parsed.key = Crypto.b64AddSlashes(hashArr[4]);
options = hashArr.slice(5);
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 5).join('/') + '/';
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
return hash;
};
return parsed;
}
if (hashArr[1] && hashArr[1] === '2') { // Version 2
parsed.version = 2;
parsed.app = hashArr[2];
parsed.mode = hashArr[3];
parsed.key = hashArr[4];
options = hashArr.slice(5);
parsed.password = options.indexOf('p') !== -1;
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 5).join('/') + '/';
if (parsed.password) { hash += 'p/'; }
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
return hash;
};
return parsed;
}
return parsed;
}
parsed.getHash = function () { return hashArr.join('/'); };
if (['media', 'file'].indexOf(type) !== -1) {
parsed.type = 'file';
if (hashArr[1] && hashArr[1] === '1') {
@@ -83,6 +174,25 @@ Version 1
parsed.key = hashArr[3].replace(/-/g, '/');
return parsed;
}
if (hashArr[1] && hashArr[1] === '2') { // Version 2
parsed.version = 2;
parsed.app = hashArr[2];
parsed.key = hashArr[3];
options = hashArr.slice(4);
parsed.password = options.indexOf('p') !== -1;
parsed.present = options.indexOf('present') !== -1;
parsed.embed = options.indexOf('embed') !== -1;
parsed.getHash = function (opts) {
var hash = hashArr.slice(0, 4).join('/') + '/';
if (parsed.password) { hash += 'p/'; }
if (opts.embed) { hash += 'embed/'; }
if (opts.present) { hash += 'present/'; }
return hash;
};
return parsed;
}
return parsed;
}
if (['user'].indexOf(type) !== -1) {
@@ -125,17 +235,9 @@ Version 1
url += ret.type + '/';
if (!ret.hashData) { return url; }
if (ret.hashData.type !== 'pad') { return url + '#' + ret.hash; }
if (ret.hashData.version !== 1) { return url + '#' + ret.hash; }
url += '#/' + ret.hashData.version +
'/' + ret.hashData.mode +
'/' + ret.hashData.channel.replace(/\//g, '-') +
'/' + ret.hashData.key.replace(/\//g, '-') +'/';
if (options.embed) {
url += 'embed/';
}
if (options.present) {
url += 'present/';
}
if (ret.hashData.version === 0) { return url + '#' + ret.hash; }
var hash = ret.hashData.getHash(options);
url += '#' + hash;
return url;
};
@@ -153,12 +255,13 @@ Version 1
return '';
});
idx = href.indexOf('/#');
if (idx === -1) { return ret; }
ret.hash = href.slice(idx + 2);
ret.hashData = parseTypeHash(ret.type, ret.hash);
return ret;
};
var getRelativeHref = Hash.getRelativeHref = function (href) {
Hash.getRelativeHref = function (href) {
if (!href) { return; }
if (href.indexOf('#') === -1) { return; }
var parsed = parsePadUrl(href);
@@ -170,11 +273,13 @@ Version 1
* - no argument: use the URL hash or create one if it doesn't exist
* - secretHash provided: use secretHash to find the keys
*/
Hash.getSecrets = function (type, secretHash) {
Hash.getSecrets = function (type, secretHash, password) {
var secret = {};
var generate = function () {
secret.keys = Crypto.createEditCryptor();
secret.key = Crypto.createEditCryptor().editKeyStr;
secret.keys = Crypto.createEditCryptor2(void 0, void 0, password);
secret.channel = base64ToHex(secret.keys.chanId);
secret.version = 2;
secret.type = type;
};
if (!secretHash && !window.location.hash) { //!/#/.test(window.location.href)) {
generate();
@@ -191,7 +296,6 @@ Version 1
parsed = pHref.hashData;
hash = pHref.hash;
}
//var parsed = parsePadUrl(window.location.href);
//var hash = secretHash || window.location.hash.slice(1);
if (hash.length === 0) {
generate();
@@ -203,9 +307,10 @@ Version 1
// Old hash
secret.channel = parsed.channel;
secret.key = parsed.key;
}
else if (parsed.version === 1) {
secret.version = 0;
} else if (parsed.version === 1) {
// New hash
secret.version = 1;
if (parsed.type === "pad") {
secret.channel = base64ToHex(parsed.channel);
if (parsed.mode === 'edit') {
@@ -222,11 +327,43 @@ Version 1
}
}
} else if (parsed.type === "file") {
// version 2 hashes are to be used for encrypted blobs
secret.channel = parsed.channel;
secret.keys = { fileKeyStr: parsed.key };
secret.channel = base64ToHex(parsed.channel);
secret.keys = {
fileKeyStr: parsed.key,
cryptKey: Nacl.util.decodeBase64(parsed.key)
};
} else if (parsed.type === "user") {
throw new Error("User hashes can't be opened (yet)");
}
} else if (parsed.version === 2) {
// New hash
secret.version = 2;
secret.type = type;
secret.password = password;
if (parsed.type === "pad") {
if (parsed.mode === 'edit') {
secret.keys = Crypto.createEditCryptor2(parsed.key, void 0, password);
secret.channel = base64ToHex(secret.keys.chanId);
secret.key = secret.keys.editKeyStr;
if (secret.channel.length !== 32 || secret.key.length !== 24) {
throw new Error("The channel key and/or the encryption key is invalid");
}
}
else if (parsed.mode === 'view') {
secret.keys = Crypto.createViewCryptor2(parsed.key, password);
secret.channel = base64ToHex(secret.keys.chanId);
if (secret.channel.length !== 32) {
throw new Error("The channel key is invalid");
}
}
} else if (parsed.type === "file") {
secret.keys = Crypto.createFileCryptor2(parsed.key, password);
secret.channel = base64ToHex(secret.keys.chanId);
secret.key = secret.keys.fileKeyStr;
if (secret.channel.length !== 48 || secret.key.length !== 24) {
throw new Error("The channel key and/or the encryption key is invalid");
}
} else if (parsed.type === "user") {
// version 2 hashes are to be used for encrypted blobs
throw new Error("User hashes can't be opened (yet)");
}
}
@@ -234,51 +371,47 @@ Version 1
return secret;
};
Hash.getHashes = function (channel, secret) {
Hash.getHashes = function (secret) {
var hashes = {};
if (!secret.keys) {
secret = JSON.parse(JSON.stringify(secret));
if (!secret.keys && !secret.key) {
console.error('e');
return hashes;
} else if (!secret.keys) {
secret.keys = {};
}
if (secret.keys.editKeyStr) {
hashes.editHash = getEditHashFromKeys(channel, secret.keys);
if (secret.keys.editKeyStr || (secret.version === 0 && secret.key)) {
hashes.editHash = getEditHashFromKeys(secret);
}
if (secret.keys.viewKeyStr) {
hashes.viewHash = getViewHashFromKeys(channel, secret.keys);
hashes.viewHash = getViewHashFromKeys(secret);
}
if (secret.keys.fileKeyStr) {
hashes.fileHash = getFileHashFromKeys(channel, secret.keys.fileKeyStr);
hashes.fileHash = getFileHashFromKeys(secret);
}
return hashes;
};
var createChannelId = Hash.createChannelId = function () {
var id = uint8ArrayToHex(Crypto.Nacl.randomBytes(16));
if (id.length !== 32 || /[^a-f0-9]/.test(id)) {
throw new Error('channel ids must consist of 32 hex characters');
}
return id;
};
Hash.createRandomHash = function () {
// 16 byte channel Id
var channelId = Util.hexToBase64(createChannelId());
// 18 byte encryption key
var key = Crypto.b64RemoveSlashes(Crypto.rand64(18));
return '/1/edit/' + [channelId, key].join('/') + '/';
};
// STORAGE
Hash.findWeaker = function (href, recents) {
var rHref = href || getRelativeHref(window.location.href);
var parsed = parsePadUrl(rHref);
Hash.findWeaker = function (href, channel, recents) {
var parsed = parsePadUrl(href);
if (!parsed.hash) { return false; }
// We can't have a weaker hash if we're already in view mode
if (parsed.hashData && parsed.hashData.mode === 'view') { return; }
var weaker;
Object.keys(recents).some(function (id) {
var pad = recents[id];
var p = parsePadUrl(pad.href);
if (pad.href || !pad.roHref) {
// This pad has an edit link, so it can't be weaker
return;
}
var p = parsePadUrl(pad.roHref);
if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
if (channel !== pad.channel) { return; } // Not the same channel
var pHash = p.hashData;
var parsedHash = parsed.hashData;
if (!parsedHash || !pHash) { return; }
@@ -287,27 +420,31 @@ Version 1
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
if (pHash.version !== parsedHash.version) { return; }
if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'view' && parsedHash.mode === 'edit') {
weaker = pad.href;
weaker = pad;
return true;
}
return;
});
return weaker;
};
var findStronger = Hash.findStronger = function (href, recents) {
var rHref = href || getRelativeHref(window.location.href);
var parsed = parsePadUrl(rHref);
Hash.findStronger = function (href, channel, recents) {
var parsed = parsePadUrl(href);
if (!parsed.hash) { return false; }
// We can't have a stronger hash if we're already in edit mode
if (parsed.hashData && parsed.hashData.mode === 'edit') { return; }
var stronger;
Object.keys(recents).some(function (id) {
var pad = recents[id];
if (!pad.href) {
// This pad doesn't have an edit link, so it can't be stronger
return;
}
var p = parsePadUrl(pad.href);
if (p.type !== parsed.type) { return; } // Not the same type
if (p.hash === parsed.hash) { return; } // Same hash, not stronger
if (channel !== pad.channel) { return; } // Not the same channel
var pHash = p.hashData;
var parsedHash = parsed.hashData;
if (!parsedHash || !pHash) { return; }
@@ -316,37 +453,20 @@ Version 1
if (pHash.type !== 'pad' && parsedHash.type !== 'pad') { return; }
if (pHash.version !== parsedHash.version) { return; }
if (pHash.channel !== parsedHash.channel) { return; }
if (pHash.mode === 'edit' && parsedHash.mode === 'view') {
stronger = pad.href;
stronger = pad;
return true;
}
return;
});
return stronger;
};
Hash.isNotStrongestStored = function (href, recents) {
return findStronger(href, recents);
};
Hash.hrefToHexChannelId = function (href) {
Hash.hrefToHexChannelId = function (href, password) {
var parsed = Hash.parsePadUrl(href);
if (!parsed || !parsed.hash) { return; }
parsed = parsed.hashData;
if (parsed.version === 0) {
return parsed.channel;
} else if (parsed.version !== 1 && parsed.version !== 2) {
console.error("parsed href had no version");
console.error(parsed);
return;
}
var channel = parsed.channel;
if (!channel) { return; }
var hex = base64ToHex(channel);
return hex;
var secret = Hash.getSecrets(parsed.type, parsed.hash, password);
return secret.channel;
};
Hash.getBlobPathFromHex = function (id) {

View File

@@ -1,3 +1,12 @@
if (!document.querySelector("#alertifyCSS")) {
// Prevent alertify from injecting CSS, we create our own in alertify.less.
// see: https://github.com/alertifyjs/alertify.js/blob/v1.0.11/src/js/alertify.js#L414
var head = document.getElementsByTagName("head")[0];
var css = document.createElement("span");
css.id = "alertifyCSS";
css.setAttribute('data-but-why', 'see: common-interface.js');
head.insertBefore(css, head.firstChild);
}
define([
'jquery',
'/customize/messages.js',
@@ -6,15 +15,18 @@ define([
'/common/common-notifier.js',
'/customize/application_config.js',
'/bower_components/alertifyjs/dist/js/alertify.js',
'/common/tippy.min.js',
'/common/tippy/tippy.min.js',
'/customize/pages.js',
'/common/hyperscript.js',
'/customize/loading.js',
'/common/test.js',
'/common/jquery-ui/jquery-ui.min.js',
'/bower_components/bootstrap-tokenfield/dist/bootstrap-tokenfield.js',
'css!/common/tippy.css',
'css!/common/tippy/tippy.css',
'css!/common/jquery-ui/jquery-ui.min.css'
], function ($, Messages, Util, Hash, Notifier, AppConfig,
Alertify, Tippy, Pages, h, Test) {
Alertify, Tippy, Pages, h, Loading, Test) {
var UI = {};
/*
@@ -141,13 +153,15 @@ define([
};
dialog.frame = function (content) {
return h('div.alertify', {
return $(h('div.alertify', {
tabindex: 1,
}, [
h('div.dialog', [
h('div', content),
])
]);
])).click(function (e) {
e.stopPropagation();
})[0];
};
/**
@@ -182,11 +196,17 @@ define([
]);
};
UI.tokenField = function (target) {
UI.tokenField = function (target, autocomplete) {
var t = {
element: target || h('input'),
};
var $t = t.tokenfield = $(t.element).tokenfield();
var $t = t.tokenfield = $(t.element).tokenfield({
autocomplete: {
source: autocomplete,
delay: 100
},
showAutocompleteOnFocus: false
});
t.getTokens = function (ignorePending) {
var tokens = $t.tokenfield('getTokens').map(function (token) {
@@ -209,10 +229,17 @@ define([
t.preventDuplicates = function (cb) {
$t.on('tokenfield:createtoken', function (ev) {
// Close the suggest list when a token is added because we're going to wipe the input
var $input = $t.closest('.tokenfield').find('.token-input');
$input.autocomplete('close');
var val;
ev.attrs.value = ev.attrs.value.toLowerCase();
if (t.getTokens(true).some(function (t) {
if (t === ev.attrs.value) { return ((val = t)); }
if (t === ev.attrs.value) {
ev.preventDefault();
return ((val = t));
}
})) {
ev.preventDefault();
if (typeof(cb) === 'function') { cb(val); }
@@ -240,7 +267,7 @@ define([
return t;
};
dialog.tagPrompt = function (tags, cb) {
dialog.tagPrompt = function (tags, existing, cb) {
var input = dialog.textInput();
var tagger = dialog.frame([
@@ -254,7 +281,7 @@ define([
dialog.nav(),
]);
var field = UI.tokenField(input).preventDuplicates(function (val) {
var field = UI.tokenField(input, existing).preventDuplicates(function (val) {
UI.warn(Messages._getKey('tags_duplicate', [val]));
});
@@ -395,7 +422,7 @@ define([
stopListening(listener);
cb();
});
listener = listenForKeys(close, close, ok);
listener = listenForKeys(close, close);
var $ok = $(ok).click(close);
document.body.appendChild(frame);
@@ -409,7 +436,8 @@ define([
cb = cb || function () {};
opt = opt || {};
var input = dialog.textInput();
var inputBlock = opt.password ? UI.passwordInput() : dialog.textInput();
var input = opt.password ? $(inputBlock).find('input')[0] : inputBlock;
input.value = typeof(def) === 'string'? def: '';
var message;
@@ -425,7 +453,7 @@ define([
var cancel = dialog.cancelButton(opt.cancel);
var frame = dialog.frame([
message,
input,
inputBlock,
dialog.nav([ cancel, ok, ]),
]);
@@ -512,6 +540,50 @@ define([
Alertify.error(Util.fixHTML(msg));
};
UI.passwordInput = function (opts, displayEye) {
opts = opts || {};
var attributes = merge({
type: 'password'
}, opts);
var input = h('input.cp-password-input', attributes);
var reveal = UI.createCheckbox('cp-password-reveal', Messages.password_show);
var eye = h('span.fa.fa-eye.cp-password-reveal');
$(reveal).find('input').on('change', function () {
if($(this).is(':checked')) {
$(input).prop('type', 'text');
$(input).focus();
return;
}
$(input).prop('type', 'password');
$(input).focus();
});
$(eye).mousedown(function () {
$(input).prop('type', 'text');
$(input).focus();
}).mouseup(function(){
$(input).prop('type', 'password');
$(input).focus();
}).mouseout(function(){
$(input).prop('type', 'password');
$(input).focus();
});
if (displayEye) {
$(reveal).hide();
} else {
$(eye).hide();
}
return h('span.cp-password-container', [
input,
reveal,
eye
]);
};
/*
* spinner
*/
@@ -539,48 +611,100 @@ define([
var LOADING = 'cp-loading';
var getRandomTip = function () {
/*var getRandomTip = function () {
if (!Messages.tips || !Object.keys(Messages.tips).length) { return ''; }
var keys = Object.keys(Messages.tips);
var rdm = Math.floor(Math.random() * keys.length);
return Messages.tips[keys[rdm]];
};*/
var loading = {
error: false,
driveState: 0,
padState: 0
};
UI.addLoadingScreen = function (config) {
config = config || {};
var loadingText = config.loadingText;
var hideTips = config.hideTips || AppConfig.hideLoadingScreenTips;
var hideLogo = config.hideLogo;
var $loading, $container;
if ($('#' + LOADING).length) {
$loading = $('#' + LOADING); //.show();
var todo = function () {
var $loading = $('#' + LOADING); //.show();
$loading.css('display', '');
$loading.removeClass('cp-loading-hidden');
$('.cp-loading-spinner-container').show();
if (!config.noProgress && !$loading.find('.cp-loading-progress').length) {
var progress = h('div.cp-loading-progress', [
h('p.cp-loading-progress-drive'),
h('p.cp-loading-progress-pad')
]);
$(progress).hide();
$loading.find('.cp-loading-container').append(progress);
} else if (config.noProgress) {
$loading.find('.cp-loading-progress').remove();
}
if (loadingText) {
$('#' + LOADING).find('p').text(loadingText);
$('#' + LOADING).find('#cp-loading-message').show().text(loadingText);
} else {
$('#' + LOADING).find('p').text('');
$('#' + LOADING).find('#cp-loading-message').hide().text('');
}
$container = $loading.find('.cp-loading-container');
loading.error = false;
};
if ($('#' + LOADING).length) {
todo();
} else {
$loading = $(Pages.loadingScreen());
$container = $loading.find('.cp-loading-container');
if (hideLogo) {
$loading.find('img').hide();
} else {
$loading.find('img').show();
}
var $spinner = $loading.find('.cp-loading-spinner-container');
$spinner.show();
$('body').append($loading);
Loading();
todo();
}
if (Messages.tips && !hideTips) {
var $loadingTip = $('<div>', {'id': 'cp-loading-tip'});
$('<span>', {'class': 'tips'}).text(getRandomTip()).appendTo($loadingTip);
$loadingTip.css({
'bottom': $('body').height()/2 - $container.height()/2 + 20 + 'px'
});
$('body').append($loadingTip);
};
UI.updateLoadingProgress = function (data, isDrive) {
var $loading = $('#' + LOADING);
if (!$loading.length || loading.error) { return; }
$loading.find('.cp-loading-progress').show();
var $progress;
if (isDrive) {
// Drive state
if (loading.driveState === -1) { return; } // Already loaded
$progress = $loading.find('.cp-loading-progress-drive');
if (!$progress.length) { return; } // Can't find the box to display data
// If state is -1, remove the box, drive is loaded
if (data.state === -1) {
loading.driveState = -1;
$progress.remove();
} else {
if (data.state < loading.driveState) { return; } // We should not display old data
// Update the current state
loading.driveState = data.state;
data.progress = data.progress || 100;
data.msg = Messages['loading_drive_'+ Math.floor(data.state)] || '';
$progress.html(data.msg);
if (data.progress) {
$progress.append(h('div.cp-loading-progress-bar', [
h('div.cp-loading-progress-bar-value', {style: 'width:'+data.progress+'%;'})
]));
}
}
} else {
// Pad state
if (loading.padState === -1) { return; } // Already loaded
$progress = $loading.find('.cp-loading-progress-pad');
if (!$progress.length) { return; } // Can't find the box to display data
// If state is -1, remove the box, pad is loaded
if (data.state === -1) {
loading.padState = -1;
$progress.remove();
} else {
if (data.state < loading.padState) { return; } // We should not display old data
// Update the current state
loading.padState = data.state;
data.progress = data.progress || 100;
data.msg = Messages['loading_pad_'+data.state] || '';
$progress.html(data.msg);
if (data.progress) {
$progress.append(h('div.cp-loading-progress-bar', [
h('div.cp-loading-progress-bar-value', {style: 'width:'+data.progress+'%;'})
]));
}
}
}
};
UI.removeLoadingScreen = function (cb) {
@@ -591,7 +715,7 @@ define([
$('#' + LOADING).addClass("cp-loading-hidden");
setTimeout(cb, 750);
//$('#' + LOADING).fadeOut(750, cb);
loading.error = false;
var $tip = $('#cp-loading-tip').css('top', '')
// loading.less sets transition-delay: $wait-time
// and transition: opacity $fadeout-time
@@ -605,18 +729,27 @@ define([
// jquery.fadeout can get stuck
};
UI.errorLoadingScreen = function (error, transparent, exitable) {
if (!$('#' + LOADING).is(':visible') || $('#' + LOADING).hasClass('cp-loading-hidden')) {
var $loading = $('#' + LOADING);
if (!$loading.is(':visible') || $loading.hasClass('cp-loading-hidden')) {
UI.addLoadingScreen({hideTips: true});
}
loading.error = true;
$loading.find('.cp-loading-progress').remove();
$('.cp-loading-spinner-container').hide();
$('#cp-loading-tip').remove();
if (transparent) { $('#' + LOADING).css('opacity', 0.8); }
$('#' + LOADING).find('p').html(error || Messages.error);
if (transparent) { $loading.css('opacity', 0.9); }
var $error = $loading.find('#cp-loading-message').show();
if (error instanceof Element) {
$error.html('').append(error);
} else {
$error.html(error || Messages.error);
}
if (exitable) {
$(window).focus();
$(window).keydown(function (e) {
if (e.which === 27) {
$('#' + LOADING).hide();
$loading.hide();
loading.error = false;
if (typeof(exitable) === "function") { exitable(); }
}
});
@@ -637,7 +770,7 @@ define([
UI.getFileIcon = function (data) {
var $icon = UI.getIcon();
if (!data) { return $icon; }
var href = data.href;
var href = data.href || data.roHref;
var type = data.type;
if (!href && !type) { return $icon; }
@@ -660,18 +793,40 @@ define([
});
};
var delay = typeof(AppConfig.tooltipDelay) === "number" ? AppConfig.tooltipDelay : 500;
$.extend(true, Tippy.defaults, {
placement: 'bottom',
performance: true,
delay: [delay, 0],
//sticky: true,
theme: 'cryptpad',
arrow: true,
maxWidth: '200px',
flip: true,
popperOptions: {
modifiers: {
preventOverflow: { boundariesElement: 'window' }
}
},
//arrowType: 'round',
dynamicTitle: true,
arrowTransform: 'scale(2)',
zIndex: 100000001
});
UI.addTooltips = function () {
var MutationObserver = window.MutationObserver;
var delay = typeof(AppConfig.tooltipDelay) === "number" ? AppConfig.tooltipDelay : 500;
var addTippy = function (i, el) {
if (el._tippy) { return; }
if (el.nodeName === 'IFRAME') { return; }
Tippy(el, {
position: 'bottom',
distance: 0,
performance: true,
delay: [delay, 0],
sticky: true
var opts = {
distance: 15
};
Array.prototype.slice.apply(el.attributes).filter(function (obj) {
return /^data-tippy-/.test(obj.name);
}).forEach(function (obj) {
opts[obj.name.slice(11)] = obj.value;
});
Tippy(el, opts);
};
// This is the robust solution to remove dangling tooltips
// The mutation observer does not always find removed nodes.
@@ -680,13 +835,13 @@ define([
var out = false;
var xId = $(x).attr('aria-describedby');
if (xId) {
if (xId.indexOf('tippy-tooltip-') === 0) {
if (xId.indexOf('tippy-') === 0) {
return true;
}
}
$(x).find('[aria-describedby]').each(function (i, el) {
var id = el.getAttribute('aria-describedby');
if (id.indexOf('tippy-tooltip-') !== 0) { return; }
if (id.indexOf('tippy-') !== 0) { return; }
out = true;
});
return out;
@@ -720,5 +875,61 @@ define([
});
};
UI.createCheckbox = Pages.createCheckbox;
UI.createRadio = Pages.createRadio;
UI.cornerPopup = function (text, actions, footer, opts) {
opts = opts || {};
var minimize = h('div.cp-corner-minimize.fa.fa-window-minimize');
var maximize = h('div.cp-corner-maximize.fa.fa-window-maximize');
var popup = h('div.cp-corner-container', [
minimize,
maximize,
h('div.cp-corner-filler', { style: "width:130px;" }),
h('div.cp-corner-filler', { style: "width:90px;" }),
h('div.cp-corner-filler', { style: "width:60px;" }),
h('div.cp-corner-filler', { style: "width:40px;" }),
h('div.cp-corner-filler', { style: "width:20px;" }),
h('div.cp-corner-text', text),
h('div.cp-corner-actions', actions),
Pages.setHTML(h('div.cp-corner-footer'), footer)
]);
$(minimize).click(function () {
$(popup).addClass('cp-minimized');
});
$(maximize).click(function () {
$(popup).removeClass('cp-minimized');
});
if (opts.hidden) {
$(popup).addClass('cp-minimized');
}
if (opts.big) {
$(popup).addClass('cp-corner-big');
}
var hide = function () {
$(popup).hide();
};
var show = function () {
$(popup).show();
};
var deletePopup = function () {
$(popup).remove();
};
$('body').append(popup);
return {
popup: popup,
hide: hide,
show: show,
delete: deletePopup
};
};
return UI;
});

View File

@@ -89,7 +89,7 @@ define([
};
/* Used to accept friend requests within apps other than /contacts/ */
Msg.addDirectMessageHandler = function (cfg) {
Msg.addDirectMessageHandler = function (cfg, href) {
var network = cfg.network;
var proxy = cfg.proxy;
if (!network) { return void console.error('Network not ready'); }
@@ -97,13 +97,12 @@ define([
var msg;
if (sender === network.historyKeeper) { return; }
try {
var parsed = Hash.parsePadUrl(window.location.href);
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets(parsed.type, parsed.hash);
if (!parsed.hashData) { return; }
var chan = parsed.hashData.channel;
var chan = secret.channel;
// Decrypt
var keyStr = parsed.hashData.key;
var cryptor = Crypto.createEditCryptor(keyStr);
var key = cryptor.cryptKey;
var key = secret.keys ? secret.keys.cryptKey : Hash.decodeBase64(secret.key);
var decryptMsg;
try {
decryptMsg = Crypto.decrypt(message, key);
@@ -113,7 +112,7 @@ define([
if (!decryptMsg) { return; }
// Parse
msg = JSON.parse(decryptMsg);
if (msg[1] !== parsed.hashData.channel) { return; }
if (msg[1] !== chan) { return; }
var msgData = msg[2];
var msgStr;
if (msg[0] === "FRIEND_REQ") {
@@ -197,15 +196,14 @@ define([
var network = cfg.network;
var netfluxId = data.netfluxId;
var parsed = Hash.parsePadUrl(data.href);
var secret = Hash.getSecrets(parsed.type, parsed.hash);
if (!parsed.hashData) { return; }
// Message
var chan = parsed.hashData.channel;
var chan = secret.channel;
var myData = createData(cfg.proxy);
var msg = ["FRIEND_REQ", chan, myData];
// Encryption
var keyStr = parsed.hashData.key;
var cryptor = Crypto.createEditCryptor(keyStr);
var key = cryptor.cryptKey;
var key = secret.keys ? secret.keys.cryptKey : Hash.decodeBase64(secret.key);
var msgStr = Crypto.encrypt(JSON.stringify(msg), key);
// Send encrypted message
if (pendingRequests.indexOf(netfluxId) === -1) {

View File

@@ -205,7 +205,7 @@ define([
if (content === oldThumbnailState) { return; }
oldThumbnailState = content;
Thumb.fromDOM(opts, function (err, b64) {
Thumb.setPadThumbnail(common, opts.href, b64);
Thumb.setPadThumbnail(common, opts.href, null, b64);
});
};
var nafa = Util.notAgainForAnother(mkThumbnail, Thumb.UPDATE_INTERVAL);
@@ -240,25 +240,25 @@ define([
Thumb.addThumbnail = function(thumb, $span, cb) {
return addThumbnail(null, thumb, $span, cb);
};
var getKey = function (href) {
var parsed = Hash.parsePadUrl(href);
return 'thumbnail-' + parsed.type + '-' + parsed.hashData.channel;
var getKey = function (type, channel) {
return 'thumbnail-' + type + '-' + channel;
};
Thumb.setPadThumbnail = function (common, href, b64, cb) {
Thumb.setPadThumbnail = function (common, href, channel, b64, cb) {
cb = cb || function () {};
var k = getKey(href);
var parsed = Hash.parsePadUrl(href);
channel = channel || common.getMetadataMgr().getPrivateData().channel;
var k = getKey(parsed.type, channel);
common.setThumbnail(k, b64, cb);
};
Thumb.displayThumbnail = function (common, href, $container, cb) {
Thumb.displayThumbnail = function (common, href, channel, password, $container, cb) {
cb = cb || function () {};
var parsed = Hash.parsePadUrl(href);
var k = getKey(href);
var k = getKey(parsed.type, channel);
var whenNewThumb = function () {
var secret = Hash.getSecrets('file', parsed.hash);
var hexFileName = Util.base64ToHex(secret.channel);
var secret = Hash.getSecrets('file', parsed.hash, password);
var hexFileName = secret.channel;
var src = Hash.getBlobPathFromHex(hexFileName);
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var key = Nacl.util.decodeBase64(cryptKey);
var key = secret.keys && secret.keys.cryptKey;
FileCrypto.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (e) {
if (e === 'XHR_ERROR') { return; }
@@ -270,7 +270,7 @@ define([
if (!v) {
v = 'EMPTY';
}
Thumb.setPadThumbnail(common, href, v, function (err) {
Thumb.setPadThumbnail(common, href, hexFileName, v, function (err) {
if (!metadata.thumbnail) { return; }
addThumbnail(err, metadata.thumbnail, $container, cb);
});

File diff suppressed because it is too large Load Diff

View File

@@ -83,6 +83,21 @@ define([], function () {
}).join('');
};
// given an array of Uint8Arrays, return a new Array with all their values
Util.uint8ArrayJoin = function (AA) {
var l = 0;
var i = 0;
for (; i < AA.length; i++) { l += AA[i].length; }
var C = new Uint8Array(l);
i = 0;
for (var offset = 0; i < AA.length; i++) {
C.set(AA[i], offset);
offset += AA[i].length;
}
return C;
};
Util.deduplicateString = function (array) {
var a = array.slice();
for(var i=0; i<a.length; i++) {
@@ -122,17 +137,14 @@ define([], function () {
else if (bytes >= oneMegabyte) { return 'MB'; }
};
// given a path, asynchronously return an arraybuffer
Util.fetch = function (src, cb) {
var done = false;
var CB = function (err, res) {
if (done) { return; }
done = true;
cb(err, res);
};
var CB = Util.once(cb);
var xhr = new XMLHttpRequest();
xhr.open("GET", src, true);
xhr.responseType = "arraybuffer";
xhr.onerror = function (err) { CB(err); };
xhr.onload = function () {
if (/^4/.test(''+this.status)) {
return CB('XHR_ERROR');
@@ -142,6 +154,22 @@ define([], function () {
xhr.send(null);
};
Util.dataURIToBlob = function (dataURI) {
var byteString = atob(dataURI.split(',')[1]);
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to an ArrayBuffer
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
// write the ArrayBuffer to a blob, and you're done
var bb = new Blob([ab], {type: mimeString});
return bb;
};
Util.throttle = function (f, ms) {
var to;
var g = function () {

View File

@@ -5,6 +5,7 @@ define([
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/outer/network-config.js',
'/bower_components/chainpad/chainpad.dist.js',
], function (Crypto, CPNetflux, Util, Hash, Realtime, NetConfig) {
var finish = function (S, err, doc) {
if (S.done) { return; }
@@ -20,9 +21,9 @@ define([
}
};
var makeConfig = function (hash) {
var makeConfig = function (hash, password) {
// We can't use cryptget with a file or a user so we can use 'pad' as hash type
var secret = Hash.getSecrets('pad', hash);
var secret = Hash.getSecrets('pad', hash, password);
if (!secret.keys) { secret.keys = secret.key; } // support old hashses
var config = {
websocketURL: NetConfig.getWebsocketURL(),
@@ -47,8 +48,10 @@ define([
if (typeof(cb) !== 'function') {
throw new Error('Cryptget expects a callback');
}
opt = opt || {};
var config = makeConfig(hash, opt.password);
var Session = { cb: cb, };
var config = makeConfig(hash);
config.onReady = function (info) {
var rt = Session.session = info.realtime;
@@ -64,9 +67,11 @@ define([
if (typeof(cb) !== 'function') {
throw new Error('Cryptput expects a callback');
}
opt = opt || {};
var config = makeConfig(hash);
var config = makeConfig(hash, opt.password);
var Session = { cb: cb, };
config.onReady = function (info) {
var realtime = Session.session = info.realtime;
Session.network = info.network;

File diff suppressed because it is too large Load Diff

View File

@@ -41,10 +41,15 @@ define([
};
renderer.image = function (href, title, text) {
if (href.slice(0,6) === '/file/') {
// DEPRECATED
// Mediatag using markdown syntax should not be used anymore so they don't support
// password-protected files
console.log('DEPRECATED: mediatag using markdown syntax!');
var parsed = Hash.parsePadUrl(href);
var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + parsed.hashData.key + '">';
var secret = Hash.getSecrets('file', parsed.hash);
var src = Hash.getBlobPathFromHex(secret.channel);
var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
if (mediaMap[src]) {
mt += mediaMap[src];
}
@@ -161,7 +166,8 @@ define([
return patch;
};
DiffMd.apply = function (newHtml, $content) {
DiffMd.apply = function (newHtml, $content, common) {
var contextMenu = common.importMediaTagMenu();
var id = $content.attr('id');
if (!id) { throw new Error("The element must have a valid id"); }
var pattern = /(<media-tag src="([^"]*)" data-crypto-key="([^"]*)">)<\/media-tag>/g;
@@ -187,6 +193,11 @@ define([
DD.apply($content[0], patch);
var $mts = $content.find('media-tag:not(:has(*))');
$mts.each(function (i, el) {
$(el).contextmenu(function (e) {
e.preventDefault();
$(contextMenu.menu).data('mediatag', $(el));
contextMenu.show(e);
});
MediaTag(el);
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {

View File

@@ -1,8 +1,9 @@
@import (once) '../customize/src/less2/include/colortheme-all.less';
@import '../customize/src/less2/include/modal.less';
@import (reference) '../customize/src/less2/include/colortheme-all.less';
@import (reference) '../customize/src/less2/include/modal.less';
.fileDialog_main () {
#fileDialog {
.modal_main();
display: none;
.cp-modal {
.fileContainer {

View File

@@ -17,8 +17,7 @@ define([], function () {
return data;
};
var identity = function (x) { return x; };
Flat.fromDOM = function (dom) {
Flat.fromDOM = function (dom, predicate, filter) {
var data = {
map: {},
};
@@ -34,14 +33,21 @@ define([], function () {
return id;
}
if (!el || !el.attributes) { return; }
if (predicate) {
if (!predicate(el)) { return; } // shortcircuit
}
id = uid();
data.map[id] = [
var temp = [
el.tagName,
getAttrs(el),
slice(el.childNodes).map(function (e) {
return process(e);
}).filter(identity)
}).filter(Boolean)
];
data.map[id] = filter? filter(temp): temp;
return id;
};

File diff suppressed because one or more lines are too long

6
www/common/jquery-ui/jquery-ui.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,94 +1,3 @@
define([], function () {
var loadingStyle = (function(){/*
#cp-loading {
transition: opacity 0.75s, visibility 0s 0.75s;
visibility: visible;
position: fixed;
z-index: 10000000;
top: 0px;
bottom: 0px;
left: 0px;
right: 0px;
background: #222;
color: #fafafa;
text-align: center;
font-size: 1.5em;
opacity: 1;
}
#cp-loading.cp-loading-hidden {
opacity: 0;
visibility: hidden;
}
#cp-loading .cp-loading-container {
margin-top: 50vh;
transform: translateY(-50%);
}
#cp-loading .cp-loading-cryptofist {
margin-left: auto;
margin-right: auto;
height: 300px;
margin-bottom: 2em;
}
@media screen and (max-height: 450px) {
#cp-loading .cp-loading-cryptofist {
display: none;
}
}
#cp-loading .cp-loading-spinner-container {
position: relative;
height: 100px;
}
#cp-loading .cp-loading-spinner-container > div {
height: 100px;
}
#cp-loading-tip {
position: fixed;
z-index: 10000000;
top: 80%;
left: 0;
right: 0;
text-align: center;
transition: opacity 750ms;
transition-delay: 3000ms;
}
@media screen and (max-height: 600px) {
#cp-loading-tip {
display: none;
}
}
#cp-loading-tip span {
background: #222;
color: #fafafa;
text-align: center;
font-size: 1.5em;
opacity: 0.7;
font-family: 'Open Sans', 'Helvetica Neue', sans-serif;
padding: 15px;
max-width: 60%;
display: inline-block;
}
*/}).toString().slice(14, -3);
var urlArgs = window.location.href.replace(/^.*\?([^\?]*)$/, function (all, x) { return x; });
var elem = document.createElement('div');
elem.setAttribute('id', 'cp-loading');
elem.innerHTML = [
'<style>',
loadingStyle,
'</style>',
'<div class="cp-loading-container">',
'<img class="cp-loading-cryptofist" src="/customize/cryptpad-new-logo-colors-logoonly.png?' + urlArgs + '">',
'<div class="cp-loading-spinner-container">',
'<span class="fa fa-circle-o-notch fa-spin fa-4x fa-fw"></span>',
'</div>',
'<p id="cp-loading-message"></p>',
'</div>'
].join('');
var intr;
var append = function () {
if (!document.body) { return; }
clearInterval(intr);
document.body.appendChild(elem);
};
intr = setInterval(append, 100);
append();
require(['/customize/loading.js'], function (Loading) {
Loading();
});

File diff suppressed because one or more lines are too long

View File

@@ -6,84 +6,6 @@ define([
], function (Crypt, FO, Hash, Realtime) {
var exp = {};
var getType = function (el) {
if (el === null) { return "null"; }
return Array.isArray(el) ? "array" : typeof(el);
};
var findAvailableKey = function (obj, key) {
if (typeof (obj[key]) === "undefined") { return key; }
var i = 1;
var nkey = key;
while (typeof (obj[nkey]) !== "undefined") {
nkey = key + '_' + i;
i++;
}
return nkey;
};
var createFromPath = function (proxy, oldFo, path, id) {
var root = proxy.drive;
if (!oldFo.isFile(id)) { return; }
var error = function (msg) {
console.error(msg || "Unable to find that path", path);
};
if (oldFo.isInTrashRoot(path)) {
id = oldFo.find(path.slice(0,3));
path.pop();
}
var next, nextRoot;
path.forEach(function (p, i) {
if (!root) { return; }
if (typeof(p) === "string") {
if (getType(root) !== "object") { root = undefined; error(); return; }
if (i === path.length - 1) {
root[Hash.createChannelId()] = id;
return;
}
next = getType(path[i+1]);
nextRoot = getType(root[p]);
if (nextRoot !== "undefined") {
if (next === "string" && nextRoot === "object" || next === "number" && nextRoot === "array") {
root = root[p];
return;
}
p = findAvailableKey(root, p);
}
if (next === "number") {
root[p] = [];
root = root[p];
return;
}
root[p] = {};
root = root[p];
return;
}
// Path contains a non-string element: it's an array index
if (typeof(p) !== "number") { root = undefined; error(); return; }
if (getType(root) !== "array") { root = undefined; error(); return; }
if (i === path.length - 1) {
if (root.indexOf(id) === -1) { root.push(id); }
return;
}
next = getType(path[i+1]);
if (next === "number") {
error('2 consecutives arrays in the user object');
root = undefined;
//root.push([]);
//root = root[root.length - 1];
return;
}
root.push({});
root = root[root.length - 1];
return;
});
};
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 || !proxyData.loggedIn) {
@@ -105,49 +27,38 @@ define([
if (parsed) {
var proxy = proxyData.proxy;
var oldFo = FO.init(parsed.drive, {
loggedIn: proxyData.loggedIn,
pinPads: function () {} // without pinPads /outer/userObject.js won't be loaded
loggedIn: true,
outer: true
});
var onMigrated = function () {
oldFo.fixFiles();
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]);
var newHrefs = Object.keys(newRecentPads).map(function (id) {
return newRecentPads[id].href;
});
oldFo.fixFiles(true);
var manager = proxyData.manager;
var oldFiles = oldFo.getFiles([oldFo.FILES_DATA]);
oldFiles.forEach(function (id) {
var href = oldRecentPads[id].href;
// Do not migrate a pad if we already have it, it would create a duplicate in the drive
if (newHrefs.indexOf(href) !== -1) { return; }
// If we have a stronger version, do not add the current href
if (Hash.findStronger(href, newRecentPads)) { return; }
// If we have a weaker version, replace the href by the new one
// NOTE: if that weaker version is in the trash, the strong one will be put in unsorted
var weaker = Hash.findWeaker(href, newRecentPads);
if (weaker) {
// Update RECENTPADS
newRecentPads.some(function (pad) {
if (pad.href === weaker) {
pad.href = href;
return true;
}
return;
var data = oldFo.getFileData(id);
var channel = data.channel;
var datas = manager.findChannel(channel, true);
// Do not migrate a pad if we already have it, it would create a duplicate
// in the drive
if (datas.length !== 0) {
// We want to merge a read-only pad: it can't be stronger than what
// we already have so abort
if (!data.href) { return; }
// We want to merge an edit pad: check if we have the same channel
// but read-only and upgrade it in that case
datas.forEach(function (pad) {
if (!pad.href) { data.href = pad.href; }
});
// Update the file in the drive
newFo.replace(weaker, href);
return;
}
// Here it means we have a new href, so we should add it to the drive at its old location
var paths = oldFo.findFile(id);
if (paths.length === 0) { return; }
// Add the file data in our array and use the id to add the file
var data = oldFo.getFileData(id);
// Here it means we have a new href, so we should add it to the drive
if (data) {
newFo.pushData(data, function (err, id) {
if (err) { return void console.error("Cannot import file:", data, err); }
createFromPath(proxy, oldFo, paths[0], id);
manager.addPad(null, data, function (err) {
if (err) {
return void console.error("Cannot import file:", data, err);
}
});
}
});
@@ -159,7 +70,12 @@ define([
Realtime.whenRealtimeSyncs(proxyData.realtime, cb);
}
};
oldFo.migrate(onMigrated);
if (oldFo && typeof(oldFo.migrate) === 'function') {
oldFo.migrate(onMigrated);
} else {
console.log('oldFo.migrate is not a function');
onMigrated();
}
return;
}
if (typeof(cb) === "function") { cb(); }

View File

@@ -34,6 +34,10 @@ define(['json.sortify'], function (Sortify) {
}
if (!metadataObj.users) { metadataObj.users = {}; }
if (!metadataLazyObj.users) { metadataLazyObj.users = {}; }
if (!metadataObj.type) { metadataObj.type = meta.doc.type; }
if (!metadataLazyObj.type) { metadataLazyObj.type = meta.doc.type; }
var mdo = {};
// We don't want to add our user data to the object multiple times.
//var containsYou = false;
@@ -60,7 +64,9 @@ define(['json.sortify'], function (Sortify) {
if (metadataObj.title !== rememberedTitle) {
rememberedTitle = metadataObj.title;
titleChangeHandlers.forEach(function (f) { f(metadataObj.title); });
titleChangeHandlers.forEach(function (f) {
f(metadataObj.title, metadataObj.defaultTitle);
});
}
changeHandlers.forEach(function (f) { f(); });

View File

@@ -1,106 +1,193 @@
define(['/common/common-feedback.js'], function (Feedback) {
define([
'/common/common-feedback.js',
'/common/common-hash.js',
'/common/common-util.js',
'/bower_components/nthen/index.js',
], function (Feedback, Hash, Util, nThen) {
// Start migration check
// Versions:
// 1: migrate pad attributes
// 2: migrate indent settings (codemirror)
return function (userObject) {
return function (userObject, cb, progress) {
var version = userObject.version || 0;
// DEPRECATED
// Migration 1: pad attributes moved to filesData
var migratePadAttributesToData = function () {
return true;
};
if (version < 1) {
migratePadAttributesToData();
}
// Migration 2: global attributes from root to 'settings' subobjects
var migrateAttributes = function () {
var drawer = 'cryptpad.userlist-drawer';
var polls = 'cryptpad.hide_poll_text';
var indentKey = 'cryptpad.indentUnit';
var useTabsKey = 'cryptpad.indentWithTabs';
var settings = userObject.settings = userObject.settings || {};
if (typeof(userObject[indentKey]) !== "undefined") {
settings.codemirror = settings.codemirror || {};
settings.codemirror.indentUnit = userObject[indentKey];
delete userObject[indentKey];
nThen(function () {
// DEPRECATED
// Migration 1: pad attributes moved to filesData
var migratePadAttributesToData = function () {
return true;
};
if (version < 1) {
migratePadAttributesToData();
}
if (typeof(userObject[useTabsKey]) !== "undefined") {
settings.codemirror = settings.codemirror || {};
settings.codemirror.indentWithTabs = userObject[useTabsKey];
delete userObject[useTabsKey];
}).nThen(function () {
// Migration 2: global attributes from root to 'settings' subobjects
var migrateAttributes = function () {
var drawer = 'cryptpad.userlist-drawer';
var polls = 'cryptpad.hide_poll_text';
var indentKey = 'cryptpad.indentUnit';
var useTabsKey = 'cryptpad.indentWithTabs';
var settings = userObject.settings = userObject.settings || {};
if (typeof(userObject[indentKey]) !== "undefined") {
settings.codemirror = settings.codemirror || {};
settings.codemirror.indentUnit = userObject[indentKey];
delete userObject[indentKey];
}
if (typeof(userObject[useTabsKey]) !== "undefined") {
settings.codemirror = settings.codemirror || {};
settings.codemirror.indentWithTabs = userObject[useTabsKey];
delete userObject[useTabsKey];
}
if (typeof(userObject[drawer]) !== "undefined") {
settings.toolbar = settings.toolbar || {};
settings.toolbar['userlist-drawer'] = userObject[drawer];
delete userObject[drawer];
}
if (typeof(userObject[polls]) !== "undefined") {
settings.poll = settings.poll || {};
settings.poll['hide-text'] = userObject[polls];
delete userObject[polls];
}
};
if (version < 2) {
migrateAttributes();
Feedback.send('Migrate-2', true);
userObject.version = version = 2;
}
if (typeof(userObject[drawer]) !== "undefined") {
settings.toolbar = settings.toolbar || {};
settings.toolbar['userlist-drawer'] = userObject[drawer];
delete userObject[drawer];
}).nThen(function () {
// Migration 3: language from localStorage to settings
var migrateLanguage = function () {
if (!localStorage.CRYPTPAD_LANG) { return; }
var l = localStorage.CRYPTPAD_LANG;
userObject.settings.language = l;
};
if (version < 3) {
migrateLanguage();
Feedback.send('Migrate-3', true);
userObject.version = version = 3;
}
if (typeof(userObject[polls]) !== "undefined") {
settings.poll = settings.poll || {};
settings.poll['hide-text'] = userObject[polls];
delete userObject[polls];
}).nThen(function () {
// Migration 4: allowUserFeedback to settings
var migrateFeedback = function () {
var settings = userObject.settings = userObject.settings || {};
if (typeof(userObject['allowUserFeedback']) !== "undefined") {
settings.general = settings.general || {};
settings.general.allowUserFeedback = userObject['allowUserFeedback'];
delete userObject['allowUserFeedback'];
}
};
if (version < 4) {
migrateFeedback();
Feedback.send('Migrate-4', true);
userObject.version = version = 4;
}
};
if (version < 2) {
migrateAttributes();
Feedback.send('Migrate-2', true);
userObject.version = version = 2;
}
// Migration 3: language from localStorage to settings
var migrateLanguage = function () {
if (!localStorage.CRYPTPAD_LANG) { return; }
var l = localStorage.CRYPTPAD_LANG;
userObject.settings.language = l;
};
if (version < 3) {
migrateLanguage();
Feedback.send('Migrate-3', true);
userObject.version = version = 3;
}
// Migration 4: allowUserFeedback to settings
var migrateFeedback = function () {
var settings = userObject.settings = userObject.settings || {};
if (typeof(userObject['allowUserFeedback']) !== "undefined") {
settings.general = settings.general || {};
settings.general.allowUserFeedback = userObject['allowUserFeedback'];
delete userObject['allowUserFeedback'];
}
};
if (version < 4) {
migrateFeedback();
Feedback.send('Migrate-4', true);
userObject.version = version = 4;
}
// Migration 5: dates to Number
var migrateDates = function () {
var data = userObject.drive && userObject.drive.filesData;
if (data) {
for (var id in data) {
if (typeof data[id].ctime !== "number") {
data[id].ctime = +new Date(data[id].ctime);
}
if (typeof data[id].atime !== "number") {
data[id].atime = +new Date(data[id].atime);
}).nThen(function () {
// Migration 5: dates to Number
var migrateDates = function () {
var data = userObject.drive && userObject.drive.filesData;
if (data) {
for (var id in data) {
if (typeof data[id].ctime !== "number") {
data[id].ctime = +new Date(data[id].ctime);
}
if (typeof data[id].atime !== "number") {
data[id].atime = +new Date(data[id].atime);
}
}
}
};
if (version < 5) {
migrateDates();
Feedback.send('Migrate-5', true);
userObject.version = version = 5;
}
};
if (version < 5) {
migrateDates();
Feedback.send('Migrate-5', true);
userObject.version = version = 5;
}
}).nThen(function (waitFor) {
var addChannelId = function () {
var data = userObject.drive.filesData;
var el, parsed;
var n = nThen(function () {});
var padsLength = Object.keys(data).length;
Object.keys(data).forEach(function (k, i) {
n = n.nThen(function (w) {
setTimeout(w(function () {
el = data[k];
parsed = Hash.parsePadUrl(el.href);
if (!el.href) { return; }
if (!el.channel) {
var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.channel = secret.channel;
progress(6, Math.round(100*i/padsLength));
console.log('Adding missing channel in filesData ', el.channel);
}
}));
});
});
n.nThen(waitFor(function () {
Feedback.send('Migrate-6', true);
userObject.version = version = 6;
}));
};
if (version < 6) {
addChannelId();
}
}).nThen(function (waitFor) {
var addRoHref = function () {
var data = userObject.drive.filesData;
var el, parsed;
var n = nThen(function () {});
var padsLength = Object.keys(data).length;
Object.keys(data).forEach(function (k, i) {
n = n.nThen(function (w) {
setTimeout(w(function () {
el = data[k];
if (!el.href || (el.roHref && false)) {
// Already migrated
return void progress(7, Math.round(100*i/padsLength));
}
parsed = Hash.parsePadUrl(el.href);
if (parsed.hashData.type !== "pad") {
// No read-only mode for files
return void progress(7, Math.round(100*i/padsLength));
}
if (parsed.hashData.mode === "view") {
// This is a read-only pad in our drive
el.roHref = el.href;
delete el.href;
console.log('Move href to roHref in filesData ', el.roHref);
} else {
var secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
var hash = Hash.getViewHashFromKeys(secret);
if (hash) {
// Version 0 won't have a view hash available
el.roHref = '/' + parsed.type + '/#' + hash;
console.log('Adding missing roHref in filesData ', el.href);
}
}
progress(6, Math.round(100*i/padsLength));
}));
});
});
n.nThen(waitFor(function () {
Feedback.send('Migrate-7', true);
userObject.version = version = 7;
}));
};
if (version < 7) {
addRoHref();
}
/*}).nThen(function (waitFor) {
// Test progress bar in the loading screen
var i = 0;
var w = waitFor();
var it = setInterval(function () {
i += 5;
if (i >= 100) { w(); clearInterval(it); i = 100;}
progress(0, i);
}, 500);
progress(0, 0);*/
}).nThen(function () {
cb();
});
};
});

View File

@@ -0,0 +1,51 @@
@import (reference) "../../customize/src/less2/include/framework.less";
// body
body.cp-app-oocell, body.cp-app-oodoc, body.cp-app-ooslide {
display: flex;
flex-flow: column;
&.cp-app-oocell {
.framework_main(
@bg-color: @colortheme_oocell-bg,
@warn-color: @colortheme_oocell-warn,
@color: @colortheme_oocell-color
);
}
&.cp-app-oodoc {
.framework_main(
@bg-color: @colortheme_oodoc-bg,
@warn-color: @colortheme_oodoc-warn,
@color: @colortheme_oodoc-color
);
}
&.cp-app-ooslide {
.framework_main(
@bg-color: @colortheme_ooslide-bg,
@warn-color: @colortheme_ooslide-warn,
@color: @colortheme_ooslide-color
);
}
#cp-toolbar {
display: flex; // We need this to remove a 3px border at the bottom of the toolbar
}
.cp-cryptpad-toolbar {
padding: 0px;
display: inline-block;
}
#cp-app-oo-container {
flex: 1;
height: 100%;
background-color: lightgrey;
display: flex;
}
#ooframe {
flex: 1;
border:none;
margin:0;
padding:0;
}
}

View File

@@ -18,7 +18,7 @@ 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',
'less!/common/onlyoffice/app-oo.less',
], function (
$,
Toolbar,

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,10 @@ define([], function () {
var unBencode = function (str) { return str.replace(/^\d+:/, ''); };
var removeCp = function (str) {
return str.replace(/^cp\|([A-Za-z0-9+\/=]{0,20}\|)?/, '');
};
var start = function (conf) {
var channel = conf.channel;
var validateKey = conf.validateKey;
@@ -68,7 +72,11 @@ define([], function () {
// shim between chainpad and netflux
var msgIn = function (peerId, msg) {
return msg.replace(/^cp\|/, '');
// NOTE: Hash version 0 contains a 32 characters nonce followed by a pipe
// at the beginning of each message on the server.
// We have to make sure our regex ignores this nonce using {0,20} (our IDs
// should only be 8 characters long)
return removeCp(msg);
};
var msgOut = function (msg) {
@@ -120,6 +128,8 @@ define([], function () {
lastKnownHash = msg.slice(0,64);
var isCp = /^cp\|/.test(msg);
var message = msgIn(peer, msg);
verbose(message);
@@ -130,7 +140,7 @@ define([], function () {
message = unBencode(message);//.slice(message.indexOf(':[') + 1);
// pass the message into Chainpad
onMessage(message);
onMessage(peer, message, validateKey, isCp);
//sframeChan.query('Q_RT_MESSAGE', message, function () { });
};
@@ -233,7 +243,6 @@ define([], function () {
};
network.on('disconnect', function (reason) {
console.log('disconnect');
//if (isIntentionallyLeaving) { return; }
if (reason === "network.disconnect() called") { return; }
onDisconnect();
@@ -256,7 +265,8 @@ define([], function () {
};
return {
start: start
start: start,
removeCp: removeCp
/*function (config) {
config.sframeChan.whenReg('EV_RT_READY', function () {
start(config);

View File

@@ -58,6 +58,14 @@ define([
localStorage[Constants.userHashKey] = sHash;
};
LocalStore.getBlockHash = function () {
return localStorage[Constants.blockHashKey];
};
LocalStore.setBlockHash = function (hash) {
localStorage[Constants.blockHashKey] = hash;
};
LocalStore.getAccountName = function () {
return localStorage[Constants.userNameKey];
};
@@ -66,10 +74,6 @@ define([
return typeof getUserHash() === "string";
};
LocalStore.login = function (hash, name, cb) {
if (!hash) { throw new Error('expected a user hash'); }
if (!name) { throw new Error('expected a user name'); }
@@ -92,10 +96,11 @@ define([
});
};
var logoutHandlers = [];
LocalStore.logout = function (cb) {
LocalStore.logout = function (cb, isDeletion) {
[
Constants.userNameKey,
Constants.userHashKey,
Constants.blockHashKey,
'loginToken',
'plan',
].forEach(function (k) {
@@ -108,13 +113,15 @@ define([
// Make sure we have an FS_hash in localStorage before reloading all the tabs
// so that we don't end up with tabs using different anon hashes
if (!LocalStore.getFSHash()) {
LocalStore.setFSHash(Hash.createRandomHash());
LocalStore.setFSHash(Hash.createRandomHash('drive'));
}
eraseTempSessionValues();
logoutHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
if (!isDeletion) {
logoutHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
}
if (typeof(AppConfig.customizeLogout) === 'function') {
return void AppConfig.customizeLogout(cb);

View File

@@ -0,0 +1,147 @@
define([
'/common/common-util.js',
'/api/config',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function (Util, ApiConfig) {
var Nacl = window.nacl;
var Block = {};
Block.join = Util.uint8ArrayJoin;
// publickey <base64 string>
// signature <base64 string>
// block <base64 string>
// [b64_public, b64_sig, b64_block [version, nonce, content]]
Block.seed = function () {
return Nacl.hash(Nacl.util.decodeUTF8('pewpewpew'));
};
// should be deterministic from a seed...
Block.genkeys = function (seed) {
if (!(seed instanceof Uint8Array)) {
throw new Error('INVALID_SEED_FORMAT');
}
if (!seed || typeof(seed.length) !== 'number' || seed.length < 64) {
throw new Error('INVALID_SEED_LENGTH');
}
var signSeed = seed.subarray(0, Nacl.sign.seedLength);
var symmetric = seed.subarray(Nacl.sign.seedLength,
Nacl.sign.seedLength + Nacl.secretbox.keyLength);
return {
sign: Nacl.sign.keyPair.fromSeed(signSeed), // 32 bytes
symmetric: symmetric, // 32 bytes ...
};
};
// (UTF8 content, keys object) => Uint8Array block
Block.encrypt = function (version, content, keys) {
var u8 = Nacl.util.decodeUTF8(content);
var nonce = Nacl.randomBytes(Nacl.secretbox.nonceLength);
return Block.join([
[0],
nonce,
Nacl.secretbox(u8, nonce, keys.symmetric)
]);
};
// (uint8Array block) => payload object
Block.decrypt = function (u8_content, keys) {
// version is currently ignored since there is only one
var nonce = u8_content.subarray(1, 1 + Nacl.secretbox.nonceLength);
var box = u8_content.subarray(1 + Nacl.secretbox.nonceLength);
var plaintext = Nacl.secretbox.open(box, nonce, keys.symmetric);
try {
return JSON.parse(Nacl.util.encodeUTF8(plaintext));
} catch (e) {
console.error(e);
return;
}
};
// (Uint8Array block) => signature
Block.sign = function (ciphertext, keys) {
return Nacl.sign.detached(Nacl.hash(ciphertext), keys.sign.secretKey);
};
Block.serialize = function (content, keys) {
// encrypt the content
var ciphertext = Block.encrypt(0, content, keys);
// generate a detached signature
var sig = Block.sign(ciphertext, keys);
// serialize {publickey, sig, ciphertext}
return {
publicKey: Nacl.util.encodeBase64(keys.sign.publicKey),
signature: Nacl.util.encodeBase64(sig),
ciphertext: Nacl.util.encodeBase64(ciphertext),
};
};
Block.remove = function (keys) {
// sign the hash of the text 'DELETE_BLOCK'
var sig = Nacl.sign.detached(Nacl.hash(
Nacl.util.decodeUTF8('DELETE_BLOCK')), keys.sign.secretKey);
return {
publicKey: Nacl.util.encodeBase64(keys.sign.publicKey),
signature: Nacl.util.encodeBase64(sig),
};
};
var urlSafeB64 = function (u8) {
return Nacl.util.encodeBase64(u8).replace(/\//g, '-');
};
Block.getBlockUrl = function (keys) {
var publicKey = urlSafeB64(keys.sign.publicKey);
// 'block/' here is hardcoded because it's hardcoded on the server
// if we want to make CryptPad work in server subfolders, we'll need
// to update this path derivation
return (ApiConfig.fileHost || window.location.origin)
+ '/block/' + publicKey.slice(0, 2) + '/' + publicKey;
};
Block.getBlockHash = function (keys) {
var absolute = Block.getBlockUrl(keys);
var symmetric = urlSafeB64(keys.symmetric);
return absolute + '#' + symmetric;
};
var decodeSafeB64 = function (b64) {
try {
return Nacl.util.decodeBase64(b64.replace(/\-/g, '/'));
} catch (e) {
console.error(e);
return;
}
};
Block.parseBlockHash = function (hash) {
if (typeof(hash) !== 'string') { return; }
var parts = hash.split('#');
if (parts.length !== 2) { return; }
try {
return {
href: parts[0],
keys: {
symmetric: decodeSafeB64(parts[1]),
}
};
} catch (e) {
console.error(e);
return;
}
};
return Block;
});

View File

@@ -0,0 +1,103 @@
define([
'/common/common-util.js',
'/common/outer/worker-channel.js',
'/common/outer/store-rpc.js',
], function (Util, Channel, SRpc) {
var msgEv = Util.mkEvent();
var sendMsg = Util.mkEvent();
var create = function () {
var Rpc = SRpc();
var postMessage = function (data) {
sendMsg.fire(data);
};
Channel.create(msgEv, postMessage, function (chan) {
var clientId = '1';
Object.keys(Rpc.queries).forEach(function (q) {
if (q === 'CONNECT') { return; }
if (q === 'JOIN_PAD') { return; }
if (q === 'SEND_PAD_MSG') { return; }
chan.on(q, function (data, cb) {
try {
Rpc.queries[q](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query ' + q);
console.error(e);
console.log(data);
}
});
});
chan.on('CONNECT', function (cfg, cb) {
// load Store here, with cfg, and pass a "query" (chan.query)
// cId is a clientId used in ServiceWorker or SharedWorker
cfg.query = function (cId, cmd, data, cb) {
cb = cb || function () {};
chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
cfg.broadcast = function (excludes, cmd, data, cb) {
cb = cb || function () {};
if (excludes.indexOf(clientId) !== -1) { return; }
chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
Rpc.queries['CONNECT'](clientId, cfg, function (data) {
if (data && data.state === "ALREADY_INIT") {
return void cb(data);
}
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
cb(data);
});
});
var chanId;
chan.on('JOIN_PAD', function (data, cb) {
chanId = data.channel;
try {
Rpc.queries['JOIN_PAD'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query JOIN_PAD');
console.error(e);
console.log(data);
}
});
chan.on('SEND_PAD_MSG', function (msg, cb) {
var data = {
msg: msg,
channel: chanId
};
try {
Rpc.queries['SEND_PAD_MSG'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query SEND_PAD_MSG');
console.error(e);
console.log(data);
}
});
}, true);
};
return {
query: function (data) {
msgEv.fire({data: data});
},
onMessage: function (cb) {
sendMsg.reg(function (data) {
setTimeout(function () {
cb(data);
});
});
},
create: create
};
});

View File

@@ -0,0 +1,178 @@
/* jshint ignore:start */
importScripts('/bower_components/requirejs/require.js');
window = self;
localStorage = {
setItem: function (k, v) { localStorage[k] = v; },
getItem: function (k) { return localStorage[k]; }
};
self.tabs = {};
var postMsg = function (client, data) {
client.postMessage(data);
};
var debug = function (msg) { console.log(msg); };
// debug = function () {};
var init = function (client, cb) {
debug('SW INIT');
require(['/api/config?cb=' + (+new Date()).toString(16)], function (ApiConfig) {
if (ApiConfig.requireConf) { require.config(ApiConfig.requireConf); }
require([
'/common/requireconfig.js'
], function (RequireConfig) {
require.config(RequireConfig());
require([
'/common/common-util.js',
'/common/outer/worker-channel.js',
'/common/outer/store-rpc.js'
], function (Util, Channel, SRpc) {
debug('SW Required ressources loaded');
var msgEv = Util.mkEvent();
if (!self.Rpc) {
self.Rpc = SRpc();
}
var Rpc = self.Rpc;
var postToClient = function (data) {
postMsg(client, data);
};
Channel.create(msgEv, postToClient, function (chan) {
debug('SW Channel created');
var clientId = client.id;
self.tabs[clientId].chan = chan;
Object.keys(Rpc.queries).forEach(function (q) {
if (q === 'CONNECT') { return; }
if (q === 'JOIN_PAD') { return; }
if (q === 'SEND_PAD_MSG') { return; }
chan.on(q, function (data, cb) {
try {
Rpc.queries[q](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query ' + q);
console.error(e);
console.log(data);
}
if (q === "DISCONNECT") {
console.log('Deleting existing store!');
delete self.Rpc;
delete self.store;
}
});
});
chan.on('CONNECT', function (cfg, cb) {
debug('SW Connect callback');
if (self.store) {
debug('Store already exists!');
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
return void cb(self.store);
}
debug('Loading new async store');
// One-time initialization (init async-store)
cfg.query = function (cId, cmd, data, cb) {
cb = cb || function () {};
self.tabs[cId].chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
cfg.broadcast = function (excludes, cmd, data, cb) {
cb = cb || function () {};
Object.keys(self.tabs).forEach(function (cId) {
if (excludes.indexOf(cId) !== -1) { return; }
self.tabs[cId].chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
});
};
Rpc.queries['CONNECT'](clientId, cfg, function (data) {
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
if (data && data.state === "ALREADY_INIT") {
return void cb(data.returned);
}
self.store = data;
cb(data);
});
});
chan.on('JOIN_PAD', function (data, cb) {
self.tabs[clientId].channelId = data.channel;
try {
Rpc.queries['JOIN_PAD'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query JOIN_PAD');
console.error(e);
console.log(data);
}
});
chan.on('SEND_PAD_MSG', function (msg, cb) {
var data = {
msg: msg,
channel: self.tabs[clientId].channelId
};
try {
Rpc.queries['SEND_PAD_MSG'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query SEND_PAD_MSG');
console.error(e);
console.log(data);
}
});
cb();
}, true);
self.tabs[client.id].msgEv = msgEv;
self.tabs[client.id].close = function () {
Rpc._removeClient(client.id);
};
});
});
});
};
self.addEventListener('message', function (e) {
var cId = e.source.id;
if (e.data === "INIT") {
if (tabs[cId]) { return; }
tabs[cId] = {
client: e.source
};
init(e.source, function () {
postMsg(e.source, 'SW_READY');
});
} else if (e.data === "CLOSE") {
if (tabs[cId] && tabs[cId].close) {
console.log('leave');
tabs[cId].close();
}
} else if (self.tabs[cId] && self.tabs[cId].msgEv) {
self.tabs[cId].msgEv.fire(e);
}
});
self.addEventListener('install', function (e) {
debug('V1 installing…');
self.skipWaiting();
});
self.addEventListener('activate', function (e) {
debug('V1 now ready to handle fetches!');
});

View File

@@ -0,0 +1,180 @@
/* jshint ignore:start */
importScripts('/bower_components/requirejs/require.js');
window = self;
localStorage = {
setItem: function (k, v) { localStorage[k] = v; },
getItem: function (k) { return localStorage[k]; }
};
self.tabs = {};
var postMsg = function (client, data) {
client.port.postMessage(data);
};
var debug = function (msg) { console.log(msg); };
// debug = function () {};
var init = function (client, cb) {
debug('SharedW INIT');
require.config({
waitSeconds: 600
});
require(['/api/config?cb=' + (+new Date()).toString(16)], function (ApiConfig) {
if (ApiConfig.requireConf) { require.config(ApiConfig.requireConf); }
require([
'/common/requireconfig.js'
], function (RequireConfig) {
require.config(RequireConfig());
require([
'/common/common-util.js',
'/common/outer/worker-channel.js',
'/common/outer/store-rpc.js'
], function (Util, Channel, SRpc) {
debug('SharedW Required ressources loaded');
var msgEv = Util.mkEvent();
if (!self.Rpc) {
self.Rpc = SRpc();
}
var Rpc = self.Rpc;
var postToClient = function (data) {
postMsg(client, data);
};
Channel.create(msgEv, postToClient, function (chan) {
debug('SharedW Channel created');
var clientId = client.id;
client.chan = chan;
Object.keys(Rpc.queries).forEach(function (q) {
if (q === 'CONNECT') { return; }
if (q === 'JOIN_PAD') { return; }
if (q === 'SEND_PAD_MSG') { return; }
chan.on(q, function (data, cb) {
try {
Rpc.queries[q](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query ' + q);
console.error(e);
console.log(data);
}
if (q === "DISCONNECT") {
console.log('Deleting existing store!');
delete self.Rpc;
delete self.store;
}
});
});
chan.on('CONNECT', function (cfg, cb) {
debug('SharedW connecting to store...');
if (self.store) {
debug('Store already exists!');
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
return void cb(self.store);
}
debug('Loading new async store');
// One-time initialization (init async-store)
cfg.query = function (cId, cmd, data, cb) {
cb = cb || function () {};
self.tabs[cId].chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
cfg.broadcast = function (excludes, cmd, data, cb) {
cb = cb || function () {};
Object.keys(self.tabs).forEach(function (cId) {
if (excludes.indexOf(cId) !== -1) { return; }
self.tabs[cId].chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
});
};
Rpc.queries['CONNECT'](clientId, cfg, function (data) {
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
if (data && data.state === "ALREADY_INIT") {
self.store = data.returned;
return void cb(data.returned);
}
self.store = data;
cb(data);
});
});
chan.on('JOIN_PAD', function (data, cb) {
client.channelId = data.channel;
try {
Rpc.queries['JOIN_PAD'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query JOIN_PAD');
console.error(e);
console.log(data);
}
});
chan.on('SEND_PAD_MSG', function (msg, cb) {
var data = {
msg: msg,
channel: client.channelId
};
try {
Rpc.queries['SEND_PAD_MSG'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query SEND_PAD_MSG');
console.error(e);
console.log(data);
}
});
cb();
}, true);
client.msgEv = msgEv;
client.close = function () {
Rpc._removeClient(client.id);
};
});
});
});
};
onconnect = function(e) {
debug('New SharedWorker client');
var port = e.ports[0];
var cId = Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
var client = self.tabs[cId] = {
id: cId,
port: port
};
port.onmessage = function (e) {
if (e.data === "INIT") {
if (client.init) { return; }
client.init = true;
init(client, function () {
postMsg(client, 'SW_READY');
});
} else if (e.data === "CLOSE") {
if (client && client.close) {
console.log('leave');
client.close();
}
} else if (client && client.msgEv) {
client.msgEv.fire(e);
}
};
};

View File

@@ -1,193 +1,103 @@
define([
'/common/outer/async-store.js'
], function (Store) {
var Rpc = {};
], function (AStore) {
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;
}
var create = function () {
var Store = AStore.create();
var Rpc = {};
var queries = Rpc.queries = {
// Ready
CONNECT: Store.init,
DISCONNECT: Store.disconnect,
CREATE_README: Store.createReadme,
MIGRATE_ANON_DRIVE: Store.migrateAnonDrive,
// 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 'REMOVE_OWNED_CHANNEL': {
Store.removeOwnedChannel(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_DELETED_PADS': {
Store.getDeletedPads(data, cb); break;
}
case 'GET_PINNED_USAGE': {
Store.getPinnedUsage(data, cb); break;
}
INIT_RPC: Store.initRpc,
UPDATE_PIN_LIMIT: Store.updatePinLimit,
GET_PIN_LIMIT: Store.getPinLimit,
CLEAR_OWNED_CHANNEL: Store.clearOwnedChannel,
REMOVE_OWNED_CHANNEL: Store.removeOwnedChannel,
UPLOAD_CHUNK: Store.uploadChunk,
UPLOAD_COMPLETE: Store.uploadComplete,
UPLOAD_STATUS: Store.uploadStatus,
UPLOAD_CANCEL: Store.uploadCancel,
WRITE_LOGIN_BLOCK: Store.writeLoginBlock,
REMOVE_LOGIN_BLOCK: Store.removeLoginBlock,
PIN_PADS: Store.pinPads,
UNPIN_PADS: Store.unpinPads,
GET_DELETED_PADS: Store.getDeletedPads,
GET_PINNED_USAGE: Store.getPinnedUsage,
// 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;
}
INIT_ANON_RPC: Store.initAnonRpc,
ANON_RPC_MESSAGE: Store.anonRpcMsg,
GET_FILE_SIZE: Store.getFileSize,
GET_MULTIPLE_FILE_SIZE: Store.getMultipleFileSize,
// 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_PAD_DATA': {
Store.getPadData(data, cb); break;
}
case 'SET_INITIAL_PATH': {
Store.setInitialPath(data); break;
}
case 'GET_STRONGER_HASH': {
Store.getStrongerHash(data, cb); break;
}
GET: Store.get,
SET: Store.set,
ADD_PAD: Store.addPad,
SET_PAD_TITLE: Store.setPadTitle,
MOVE_TO_TRASH: Store.moveToTrash,
RESET_DRIVE: Store.resetDrive,
GET_METADATA: Store.getMetadata,
IS_ONLY_IN_SHARED_FOLDER: Store.isOnlyInSharedFolder,
SET_DISPLAY_NAME: Store.setDisplayName,
SET_PAD_ATTRIBUTE: Store.setPadAttribute,
GET_PAD_ATTRIBUTE: Store.getPadAttribute,
SET_ATTRIBUTE: Store.setAttribute,
GET_ATTRIBUTE: Store.getAttribute,
LIST_ALL_TAGS: Store.listAllTags,
GET_TEMPLATES: Store.getTemplates,
GET_SECURE_FILES_LIST: Store.getSecureFilesList,
GET_PAD_DATA: Store.getPadData,
GET_STRONGER_HASH: Store.getStrongerHash,
INCREMENT_TEMPLATE_USE: Store.incrementTemplateUse,
GET_SHARED_FOLDER: Store.getSharedFolder,
ADD_SHARED_FOLDER: Store.addSharedFolder,
// Messaging
case 'INVITE_FROM_USERLIST': {
Store.inviteFromUserlist(data, cb); break;
}
INVITE_FROM_USERLIST: Store.inviteFromUserlist,
ADD_DIRECT_MESSAGE_HANDLERS: Store.addDirectMessageHandlers,
// 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;
}
CONTACTS_GET_FRIEND_LIST: Store.messenger.getFriendList,
CONTACTS_GET_MY_INFO: Store.messenger.getMyInfo,
CONTACTS_GET_FRIEND_INFO: Store.messenger.getFriendInfo,
CONTACTS_REMOVE_FRIEND: Store.messenger.removeFriend,
CONTACTS_OPEN_FRIEND_CHANNEL: Store.messenger.openFriendChannel,
CONTACTS_GET_FRIEND_STATUS: Store.messenger.getFriendStatus,
CONTACTS_GET_MORE_HISTORY: Store.messenger.getMoreHistory,
CONTACTS_SEND_MESSAGE: Store.messenger.sendMessage,
CONTACTS_SET_CHANNEL_HEAD: Store.messenger.setChannelHead,
// Pad
case 'SEND_PAD_MSG': {
Store.sendPadMsg(data, cb); break;
}
case 'JOIN_PAD': {
Store.joinPad(data, cb); break;
}
case 'GET_FULL_HISTORY': {
Store.getFullHistory(data, cb); break;
}
SEND_PAD_MSG: Store.sendPadMsg,
JOIN_PAD: Store.joinPad,
LEAVE_PAD: Store.leavePad,
GET_FULL_HISTORY: Store.getFullHistory,
GET_HISTORY_RANGE: Store.getHistoryRange,
IS_NEW_CHANNEL: Store.isNewChannel,
// Drive
case 'DRIVE_USEROBJECT': {
Store.userObjectCommand(data, cb); break;
}
// Settings
case 'DELETE_ACCOUNT': {
Store.deleteAccount(data, cb); break;
}
case 'IS_NEW_CHANNEL': {
Store.isNewChannel(data, cb); break;
}
default: {
console.error("UNHANDLED_STORE_RPC");
DRIVE_USEROBJECT: Store.userObjectCommand,
// Settings,
DELETE_ACCOUNT: Store.deleteAccount,
};
break;
Rpc.query = function (cmd, data, cb) {
if (queries[cmd]) {
queries[cmd]('0', data, cb);
} else {
console.error('UNHANDLED_STORE_RPC');
}
}
};
// Internal calls
Rpc._removeClient = Store._removeClient;
Rpc._subscribeToDrive = Store._subscribeToDrive;
Rpc._subscribeToMessenger = Store._subscribeToMessenger;
return Rpc;
};
return Rpc;
return create;
});

View File

@@ -0,0 +1,4 @@
if (!self.crypto && !self.msCrypto) {
throw new Error("E_NOCRYPTO");
}
self.postMessage("OK");

View File

@@ -1,8 +1,9 @@
define([
'/file/file-crypto.js',
'/common/common-hash.js',
'/bower_components/nthen/index.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function (FileCrypto, Hash) {
], function (FileCrypto, Hash, nThen) {
var Nacl = window.nacl;
var module = {};
@@ -10,83 +11,123 @@ define([
var u8 = file.blob; // This is not a blob but a uint8array
var metadata = file.metadata;
var owned = file.owned;
// if it exists, path contains the new pad location in the drive
var path = file.path;
var key = Nacl.randomBytes(32);
var next = FileCrypto.encrypt(u8, metadata, key);
var password = file.password;
var forceSave = file.forceSave;
var hash, secret, key, id, href;
var estimate = FileCrypto.computeEncryptedSize(u8.length, metadata);
var getNewHash = function () {
hash = Hash.createRandomHash('file', password);
secret = Hash.getSecrets('file', hash, password);
key = secret.keys.cryptKey;
id = secret.channel;
href = '/file/#' + hash;
};
var sendChunk = function (box, cb) {
var enc = Nacl.util.encodeBase64(box);
common.uploadChunk(enc, function (e, msg) {
cb(e, msg);
var getValidHash = function (cb) {
getNewHash();
common.getFileSize(href, password, function (err, size) {
if (err || typeof(size) !== "number") { throw new Error(err || "Invalid size!"); }
if (size === 0) { return void cb(); }
getValidHash();
});
};
var actual = 0;
var again = function (err, box) {
if (err) { throw new Error(err); }
if (box) {
actual += box.length;
var progressValue = (actual / estimate * 100);
updateProgress(progressValue);
var edPublic;
nThen(function (waitFor) {
// Generate a hash and check if the resulting id is valid (not already used)
getValidHash(waitFor());
}).nThen(function (waitFor) {
if (!owned) { return; }
common.getMetadata(waitFor(function (err, m) {
edPublic = m.priv.edPublic;
metadata.owners = [edPublic];
}));
}).nThen(function () {
var next = FileCrypto.encrypt(u8, metadata, key);
return void sendChunk(box, function (e) {
if (e) { return console.error(e); }
next(again);
var estimate = FileCrypto.computeEncryptedSize(u8.length, metadata);
var sendChunk = function (box, cb) {
var enc = Nacl.util.encodeBase64(box);
common.uploadChunk(enc, function (e, msg) {
cb(e, msg);
});
}
};
if (actual !== estimate) {
console.error('Estimated size does not match actual size');
}
var actual = 0;
var again = function (err, box) {
if (err) { throw new Error(err); }
if (box) {
actual += box.length;
var progressValue = (actual / estimate * 100);
updateProgress(progressValue);
// if not box then done
common.uploadComplete(function (e, id) {
if (e) { return void console.error(e); }
var uri = ['', 'blob', id.slice(0,2), id].join('/');
console.log("encrypted blob is now available as %s", uri);
var b64Key = Nacl.util.encodeBase64(key);
var hash = Hash.getFileHashFromKeys(id, b64Key);
var href = '/file/#' + hash;
var title = metadata.name;
if (noStore) { return void onComplete(href); }
common.setPadTitle(title || "", href, path, function (err) {
if (err) { return void console.error(err); }
onComplete(href);
common.setPadAttribute('fileType', metadata.type, null, href);
});
});
};
common.uploadStatus(estimate, function (e, pending) {
if (e) {
console.error(e);
onError(e);
return;
}
if (pending) {
return void onPending(function () {
// if the user wants to cancel the pending upload to execute that one
common.uploadCancel(function (e, res) {
if (e) {
return void console.error(e);
}
console.log(res);
return void sendChunk(box, function (e) {
if (e) { return console.error(e); }
next(again);
});
}
if (actual !== estimate) {
console.error('Estimated size does not match actual size');
}
// if not box then done
common.uploadComplete(id, owned, function (e) {
if (e) { return void console.error(e); }
var uri = ['', 'blob', id.slice(0,2), id].join('/');
console.log("encrypted blob is now available as %s", uri);
var title = metadata.name;
if (noStore) { return void onComplete(href); }
var data = {
title: title || "",
href: href,
path: path,
password: password,
channel: id,
owners: metadata.owners,
forceSave: forceSave
};
common.setPadTitle(data, function (err) {
if (err) { return void console.error(err); }
onComplete(href);
common.setPadAttribute('fileType', metadata.type, null, href);
common.setPadAttribute('owners', metadata.owners, null, href);
});
});
}
next(again);
};
common.uploadStatus(estimate, function (e, pending) {
if (e) {
console.error(e);
onError(e);
return;
}
if (pending) {
return void onPending(function () {
// if the user wants to cancel the pending upload to execute that one
common.uploadCancel(estimate, function (e) {
if (e) {
return void console.error(e);
}
next(again);
});
});
}
next(again);
});
});
};
return module;
});

View File

@@ -14,15 +14,11 @@ define([
};
module.init = function (config, exp, files) {
var unpinPads = config.unpinPads || function () {
console.error("unpinPads was not provided");
};
var pinPads = config.pinPads;
var removeOwnedChannel = config.removeOwnedChannel || function () {
console.error("removeOwnedChannel was not provided");
};
var loggedIn = config.loggedIn;
var workgroup = config.workgroup;
var sharedFolder = config.sharedFolder;
var edPublic = config.edPublic;
var ROOT = exp.ROOT;
@@ -31,6 +27,7 @@ define([
var UNSORTED = exp.UNSORTED;
var TRASH = exp.TRASH;
var TEMPLATE = exp.TEMPLATE;
var SHARED_FOLDERS = exp.SHARED_FOLDERS;
var debug = exp.debug;
@@ -50,35 +47,31 @@ define([
var data = exp.getFileData(id);
cb(null, clone(data[attr]));
};
var removePadAttribute = exp.removePadAttribute = function (f) {
if (typeof(f) !== 'string') {
console.error("Can't find pad attribute for an undefined pad");
return;
}
Object.keys(files).forEach(function (key) {
var hash = f.indexOf('#') !== -1 ? f.slice(f.indexOf('#') + 1) : null;
if (hash && key.indexOf(hash) === 0) {
exp.debug("Deleting pad attribute in the realtime object");
delete files[key];
}
});
};
exp.pushData = function (data, cb) {
if (typeof cb !== "function") { cb = function () {}; }
var todo = function () {
var id = Util.createRandomInteger();
files[FILES_DATA][id] = data;
cb(null, id);
};
if (!loggedIn || !AppConfig.enablePinning || config.testMode) {
return void todo();
var id = Util.createRandomInteger();
files[FILES_DATA][id] = data;
cb(null, id);
};
exp.pushSharedFolder = function (data, cb) {
if (typeof cb !== "function") { cb = function () {}; }
// Check if we already have this shared folder in our drive
if (Object.keys(files[SHARED_FOLDERS]).some(function (k) {
return files[SHARED_FOLDERS][k].channel === data.channel;
})) {
return void cb ('EEXISTS');
}
if (!pinPads) { return; }
pinPads([Hash.hrefToHexChannelId(data.href)], function (obj) {
if (obj && obj.error) { return void cb(obj.error); }
todo();
});
// Add the folder
if (!loggedIn || !AppConfig.enablePinning || config.testMode) {
return void cb("EAUTH");
}
var id = Util.createRandomInteger();
files[SHARED_FOLDERS][id] = data;
cb(null, id);
};
// FILES DATA
@@ -87,21 +80,21 @@ define([
};
// Find files in FILES_DATA that are not anymore in the drive, and remove them from
// FILES_DATA. If there are owned pads, remove them from server too, unless the flag tells
// us they're already removed
exp.checkDeletedFiles = function (isOwnPadRemoved) {
// Nothing in FILES_DATA for workgroups
if (workgroup || (!loggedIn && !config.testMode)) { return; }
// FILES_DATA. If there are owned pads, remove them from server too.
exp.checkDeletedFiles = function (cb) {
if (!loggedIn && !config.testMode) { return void cb(); }
var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]);
var toClean = [];
exp.getFiles([FILES_DATA]).forEach(function (id) {
var ownedRemoved = [];
exp.getFiles([FILES_DATA, SHARED_FOLDERS]).forEach(function (id) {
if (filesList.indexOf(id) === -1) {
var fd = exp.getFileData(id);
var channelId = fd && fd.href && Hash.hrefToHexChannelId(fd.href);
var fd = exp.isSharedFolder(id) ? files[SHARED_FOLDERS][id] : exp.getFileData(id);
var channelId = fd.channel;
// If trying to remove an owned pad, remove it from server also
if (!isOwnPadRemoved &&
fd.owners && fd.owners.indexOf(edPublic) !== -1 && channelId) {
if (!sharedFolder && fd.owners && fd.owners.indexOf(edPublic) !== -1
&& channelId) {
if (channelId) { ownedRemoved.push(channelId); }
removeOwnedChannel(channelId, function (obj) {
if (obj && obj.error) {
// If the error is that the file is already removed, nothing to
@@ -111,20 +104,21 @@ define([
// RPC may not be responding
// Send a report that can be handled manually
console.error(obj.error);
Feedback.send('ERROR_DELETING_OWNED_PAD=' + channelId, true);
Feedback.send('ERROR_DELETING_OWNED_PAD=' + channelId + '|' + obj.error, true);
}
});
}
if (fd.lastVersion) { toClean.push(Hash.hrefToHexChannelId(fd.lastVersion)); }
if (channelId) { toClean.push(channelId); }
spliceFileData(id);
if (exp.isSharedFolder(id)) {
delete files[SHARED_FOLDERS][id];
} else {
spliceFileData(id);
}
}
});
if (!toClean.length) { return; }
unpinPads(toClean, function (response) {
if (response && response.error) { return console.error(response.error); }
// console.error(response);
});
if (!toClean.length) { return void cb(); }
cb(null, toClean, ownedRemoved);
};
var deleteHrefs = function (ids) {
ids.forEach(function (obj) {
@@ -138,7 +132,7 @@ define([
files[TRASH][obj.name].splice(idx, 1);
});
};
exp.deleteMultiplePermanently = function (paths, nocheck, isOwnPadRemoved) {
exp.deleteMultiplePermanently = function (paths, nocheck, cb) {
var hrefPaths = paths.filter(function(x) { return exp.isPathIn(x, ['hrefArray']); });
var rootPaths = paths.filter(function(x) { return exp.isPathIn(x, [ROOT]); });
var trashPaths = paths.filter(function(x) { return exp.isPathIn(x, [TRASH]); });
@@ -146,14 +140,11 @@ define([
if (!loggedIn && !config.testMode) {
allFilesPaths.forEach(function (path) {
var el = exp.find(path);
if (!el) { return; }
var id = exp.getIdFromHref(el.href);
var id = path[1];
if (!id) { return; }
spliceFileData(id);
removePadAttribute(el.href);
});
return;
return void cb();
}
var ids = [];
@@ -193,11 +184,69 @@ define([
deleteMultipleTrashRoot(trashRoot);
// In some cases, we want to remove pads from a location without removing them from
// OLD_FILES_DATA (replaceHref)
if (!nocheck) { exp.checkDeletedFiles(isOwnPadRemoved); }
// FILES_DATA (replaceHref)
if (!nocheck) { exp.checkDeletedFiles(cb); }
else { cb(); }
};
// Move
// From another drive
exp.copyFromOtherDrive = function (path, element, data, key) {
// Copy files data
// We have to remove pads that are already in the current proxy to make sure
// we won't create duplicates
var toRemove = [];
Object.keys(data).forEach(function (id) {
id = Number(id);
// Find and maybe update existing pads with the same channel id
var d = data[id];
var found = false;
for (var i in files[FILES_DATA]) {
if (files[FILES_DATA][i].channel === d.channel) {
// Update href?
if (!files[FILES_DATA][i].href) { files[FILES_DATA][i].href = d.href; }
found = true;
break;
}
}
if (found) {
toRemove.push(id);
return;
}
files[FILES_DATA][id] = data[id];
});
// Remove existing pads from the "element" variable
if (exp.isFile(element) && toRemove.indexOf(element) !== -1) {
exp.log(Messages.sharedFolders_duplicate);
return;
} else if (exp.isFolder(element)) {
var _removeExisting = function (root) {
for (var k in root) {
if (exp.isFile(root[k])) {
if (toRemove.indexOf(root[k]) !== -1) {
exp.log(Messages.sharedFolders_duplicate);
delete root[k];
}
} else if (exp.isFolder(root[k])) {
_removeExisting(root[k]);
}
}
};
_removeExisting(element);
}
// Copy file or folder
var newParent = exp.find(path);
var tempName = exp.isFile(element) ? Hash.createChannelId() : key;
var newName = exp.getAvailableName(newParent, tempName);
newParent[newName] = element;
};
// From the same drive
var pushToTrash = function (name, element, path) {
var trash = files[TRASH];
if (typeof(trash[name]) === "undefined") { trash[name] = []; }
@@ -260,7 +309,6 @@ define([
if (!id) { return; }
if (!loggedIn && !config.testMode) {
// delete permanently
exp.removePadAttribute(href);
spliceFileData(id);
return;
}
@@ -269,14 +317,7 @@ define([
};
// REPLACE
exp.replace = function (o, n) {
var idO = exp.getIdFromHref(o);
if (!idO || !exp.isFile(idO)) { return; }
var data = exp.getFileData(idO);
if (!data) { return; }
data.href = n;
};
// If all the occurences of an href are in the trash, remvoe them and add the file in root.
// If all the occurences of an href are in the trash, remove them and add the file in root.
// This is use with setPadTitle when we open a stronger version of a deleted pad
exp.restoreHref = function (href) {
var idO = exp.getIdFromHref(href);
@@ -301,9 +342,9 @@ define([
};
exp.add = function (id, path) {
// TODO WW
if (!loggedIn && !config.testMode) { return; }
var data = files[FILES_DATA][id];
id = Number(id);
var data = files[FILES_DATA][id] || files[SHARED_FOLDERS][id];
if (!data || typeof(data) !== "object") { return; }
var newPath = path, parentEl;
if (path && !Array.isArray(path)) {
@@ -407,7 +448,6 @@ define([
});
delete files[OLD_FILES_DATA];
delete files.migrate;
console.log('done');
todo();
};
if (exp.rt) {
@@ -427,7 +467,7 @@ define([
migrateToNewFormat(cb);
};
exp.fixFiles = function () {
exp.fixFiles = function (silent) {
// Explore the tree and check that everything is correct:
// * 'root', 'trash', 'unsorted' and 'filesData' exist and are objects
// * ROOT: Folders are objects, files are href
@@ -436,6 +476,9 @@ define([
// - Dates (adate, cdate) can be parsed/formatted
// - All files in filesData should be either in 'root', 'trash' or 'unsorted'. If that's not the case, copy the fily to 'unsorted'
// * TEMPLATE: Contains only files (href), and does not contains files that are in ROOT
if (silent) { debug = function () {}; }
debug("Cleaning file system...");
var before = JSON.stringify(files);
@@ -471,6 +514,7 @@ define([
}
};
var fixTrashRoot = function () {
if (sharedFolder) { return; }
if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; }
var tr = files[TRASH];
var toClean;
@@ -514,6 +558,7 @@ define([
}
};
var fixTemplate = function () {
if (sharedFolder) { return; }
if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; }
files[TEMPLATE] = Util.deduplicateString(files[TEMPLATE].slice());
var us = files[TEMPLATE];
@@ -545,7 +590,7 @@ define([
});
};
var fixFilesData = function () {
if (typeof files[FILES_DATA] !== "object") { debug("OLD_FILES_DATA was not an object"); files[FILES_DATA] = {}; }
if (typeof files[FILES_DATA] !== "object") { debug("FILES_DATA was not an object"); files[FILES_DATA] = {}; }
var fd = files[FILES_DATA];
var rootFiles = exp.getFiles([ROOT, TRASH, 'hrefArray']);
var root = exp.find([ROOT]);
@@ -553,32 +598,73 @@ define([
for (var id in fd) {
id = Number(id);
var el = fd[id];
// Clean corrupted data
if (!el || typeof(el) !== "object") {
debug("An element in filesData was not an object.", el);
toClean.push(id);
continue;
}
if (!el.href) {
// Clean missing href
if (!el.href && !el.roHref) {
debug("Removing an element in filesData with a missing href.", el);
toClean.push(id);
continue;
}
if (/^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); }
if (!el.ctime) { el.ctime = el.atime; }
var parsed = Hash.parsePadUrl(el.href);
if (!el.title) { el.title = Hash.getDefaultName(parsed); }
var parsed = Hash.parsePadUrl(el.href || el.roHref);
var secret;
// Clean invalid hash
if (!parsed.hash) {
debug("Removing an element in filesData with a invalid href.", el);
toClean.push(id);
continue;
}
// Clean invalid type
if (!parsed.type) {
debug("Removing an element in filesData with a invalid type.", el);
toClean.push(id);
continue;
}
// If we have an edit link, check the view link
if (el.href && parsed.hashData.type === "pad") {
if (parsed.hashData.mode === "view") {
el.roHref = el.href;
delete el.href;
} else if (!el.roHref) {
secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret);
} else {
var parsed2 = Hash.parsePadUrl(el.roHref);
if (!parsed2.hash || !parsed2.type) {
secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
el.roHref = '/' + parsed.type + '/#' + Hash.getViewHashFromKeys(secret);
}
}
}
// Fix href
if (el.href && /^https*:\/\//.test(el.href)) { el.href = Hash.getRelativeHref(el.href); }
// Fix creation time
if (!el.ctime) { el.ctime = el.atime; }
// Fix title
if (!el.title) { el.title = Hash.getDefaultName(parsed); }
// Fix channel
if (!el.channel) {
try {
if (!secret) {
secret = Hash.getSecrets(parsed.type, parsed.hash, el.password);
}
el.channel = secret.channel;
console.log(el);
debug('Adding missing channel in filesData ', el.channel);
} catch (e) {
console.error(e);
}
}
if ((loggedIn || config.testMode) && rootFiles.indexOf(id) === -1) {
debug("An element in filesData was not in ROOT, TEMPLATE or TRASH.", id, el);
var newName = Hash.createChannelId();
@@ -590,6 +676,21 @@ define([
spliceFileData(id);
});
};
var fixSharedFolders = function () {
if (sharedFolder) { return; }
if (typeof(files[SHARED_FOLDERS]) !== "object") { debug("SHARED_FOLDER was not an object"); files[SHARED_FOLDERS] = {}; }
var sf = files[SHARED_FOLDERS];
var rootFiles = exp.getFiles([ROOT]);
var root = exp.find([ROOT]);
for (var id in sf) {
id = Number(id);
if (rootFiles.indexOf(id) === -1) {
console.log('missing' + id);
var newName = Hash.createChannelId();
root[newName] = id;
}
}
};
var fixDrive = function () {
Object.keys(files).forEach(function (key) {
@@ -599,11 +700,10 @@ define([
fixRoot();
fixTrashRoot();
if (!workgroup) {
fixTemplate();
fixFilesData();
}
fixTemplate();
fixFilesData();
fixDrive();
fixSharedFolders();
if (JSON.stringify(files) !== before) {
debug("Your file system was corrupted. It has been cleaned so that the pads you visit can be stored safely");

View File

@@ -0,0 +1,103 @@
/* jshint ignore:start */
importScripts('/bower_components/requirejs/require.js');
window = self;
localStorage = {
setItem: function (k, v) { localStorage[k] = v; },
getItem: function (k) { return localStorage[k]; }
};
require(['/api/config?cb=' + (+new Date()).toString(16)], function (ApiConfig) {
if (ApiConfig.requireConf) { require.config(ApiConfig.requireConf); }
require([
'/common/requireconfig.js'
], function (RequireConfig) {
require.config(RequireConfig());
require([
'/common/common-util.js',
'/common/outer/worker-channel.js',
'/common/outer/store-rpc.js'
], function (Util, Channel, SRpc) {
var msgEv = Util.mkEvent();
var Rpc = SRpc();
Channel.create(msgEv, postMessage, function (chan) {
var clientId = '1';
Object.keys(Rpc.queries).forEach(function (q) {
if (q === 'CONNECT') { return; }
if (q === 'JOIN_PAD') { return; }
if (q === 'SEND_PAD_MSG') { return; }
chan.on(q, function (data, cb) {
try {
Rpc.queries[q](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query ' + q);
console.error(e);
console.log(data);
}
});
});
chan.on('CONNECT', function (cfg, cb) {
// load Store here, with cfg, and pass a "query" (chan.query)
// cId is a clientId used in ServiceWorker or SharedWorker
cfg.query = function (cId, cmd, data, cb) {
cb = cb || function () {};
chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
cfg.broadcast = function (excludes, cmd, data, cb) {
cb = cb || function () {};
if (excludes.indexOf(clientId) !== -1) { return; }
chan.query(cmd, data, function (err, data2) {
if (err) { return void cb({error: err}); }
cb(data2);
});
};
Rpc.queries['CONNECT'](clientId, cfg, function (data) {
if (data && data.state === "ALREADY_INIT") {
return void cb(data);
}
if (cfg.driveEvents) {
Rpc._subscribeToDrive(clientId);
}
if (cfg.messenger) {
Rpc._subscribeToMessenger(clientId);
}
cb(data);
});
});
var chanId;
chan.on('JOIN_PAD', function (data, cb) {
chanId = data.channel;
try {
Rpc.queries['JOIN_PAD'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query JOIN_PAD');
console.error(e);
console.log(data);
}
});
chan.on('SEND_PAD_MSG', function (msg, cb) {
var data = {
msg: msg,
channel: chanId
};
try {
Rpc.queries['SEND_PAD_MSG'](clientId, data, cb);
} catch (e) {
console.error('Error in webworker when executing query SEND_PAD_MSG');
console.error(e);
console.log(data);
}
});
}, true);
onmessage = function (e) {
msgEv.fire(e);
};
});
});
});

View File

@@ -0,0 +1,151 @@
// This file provides the API for the channel for talking to and from the sandbox iframe.
define([
//'/common/sframe-protocol.js',
'/common/common-util.js'
], function (/*SFrameProtocol,*/ Util) {
var mkTxid = function () {
return Math.random().toString(16).replace('0.', '') + Math.random().toString(16).replace('0.', '');
};
var create = function (onMsg, postMsg, cb, isWorker) {
var chanLoaded;
var waitingData;
if (!isWorker) {
chanLoaded = false;
waitingData = [];
onMsg.reg(function (data) {
if (chanLoaded) { return; }
waitingData.push(data);
});
}
var evReady = Util.mkEvent(true);
var handlers = {};
var queries = {};
// list of handlers which are registered from the other side...
var insideHandlers = [];
var callWhenRegistered = {};
var chan = {};
// Send a query. channel.query('Q_SOMETHING', { args: "whatever" }, function (reply) { ... });
chan.query = function (q, content, cb, opts) {
var txid = mkTxid();
opts = opts || {};
var to = opts.timeout || 30000;
var timeout = setTimeout(function () {
delete queries[txid];
//console.log("Timeout making query " + q);
}, to);
queries[txid] = function (data, msg) {
clearTimeout(timeout);
delete queries[txid];
cb(undefined, data.content, msg);
};
evReady.reg(function () {
postMsg(JSON.stringify({
txid: txid,
content: content,
q: q
}));
});
};
// Fire an event. channel.event('EV_SOMETHING', { args: "whatever" });
var event = chan.event = function (e, content) {
evReady.reg(function () {
postMsg(JSON.stringify({ content: content, q: e }));
});
};
// Be notified on query or event. channel.on('EV_SOMETHING', function (args, reply) { ... });
// If the type is a query, your handler will be invoked with a reply function that takes
// one argument (the content to reply with).
chan.on = function (queryType, handler, quiet) {
(handlers[queryType] = handlers[queryType] || []).push(function (data, msg) {
handler(data.content, function (replyContent) {
postMsg(JSON.stringify({
txid: data.txid,
content: replyContent
}));
}, msg);
});
if (!quiet) {
event('EV_REGISTER_HANDLER', queryType);
}
};
// If a particular handler is registered, call the callback immediately, otherwise it will be called
// when that handler is first registered.
// channel.whenReg('Q_SOMETHING', function () { ...query Q_SOMETHING?... });
chan.whenReg = function (queryType, cb, always) {
var reg = always;
if (insideHandlers.indexOf(queryType) > -1) {
cb();
} else {
reg = true;
}
if (reg) {
(callWhenRegistered[queryType] = callWhenRegistered[queryType] || []).push(cb);
}
};
// Same as whenReg except it will invoke every time there is another registration, not just once.
chan.onReg = function (queryType, cb) { chan.whenReg(queryType, cb, true); };
chan.on('EV_REGISTER_HANDLER', function (content) {
if (callWhenRegistered[content]) {
callWhenRegistered[content].forEach(function (f) { f(); });
delete callWhenRegistered[content];
}
insideHandlers.push(content);
});
chan.whenReg('EV_REGISTER_HANDLER', evReady.fire);
// Make sure both iframes are ready
var isReady =false;
chan.onReady = function (h) {
if (isReady) {
return void h();
}
if (typeof(h) !== "function") { return; }
chan.on('EV_RPC_READY', function () { isReady = true; h(); });
};
chan.ready = function () {
chan.whenReg('EV_RPC_READY', function () {
chan.event('EV_RPC_READY');
});
};
onMsg.reg(function (msg) {
var data = JSON.parse(msg.data);
if (typeof(data.q) === 'string' && handlers[data.q]) {
handlers[data.q].forEach(function (f) {
f(data || JSON.parse(msg.data), msg);
data = undefined;
});
} else if (typeof(data.q) === 'undefined' && queries[data.txid]) {
queries[data.txid](data, msg);
} else {
console.log("DROP Unhandled message");
console.log(msg.data, isWorker);
console.log(msg);
}
});
if (isWorker) {
evReady.fire();
} else {
chanLoaded = true;
waitingData.forEach(function (d) {
onMsg.fire(d);
});
waitingData = [];
}
cb(chan);
};
return { create: create };
});

View File

@@ -151,7 +151,7 @@ define([
};
exp.removeOwnedChannel = function (channel, cb) {
if (typeof(channel) !== 'string' || channel.length !== 32) {
if (typeof(channel) !== 'string' || [32,48].indexOf(channel.length) === -1) {
// can't use this on files because files can't be owned...
return void cb('INVALID_ARGUMENTS');
}
@@ -165,8 +165,30 @@ define([
});
};
exp.uploadComplete = function (cb) {
rpc.send('UPLOAD_COMPLETE', null, function (e, res) {
exp.removePins = function (cb) {
rpc.send('REMOVE_PINS', undefined, function (e, response) {
if (e) { return void cb(e); }
if (response && response.length && response[0] === "OK") {
cb();
} else {
cb('INVALID_RESPONSE');
}
});
};
exp.uploadComplete = function (id, cb) {
rpc.send('UPLOAD_COMPLETE', id, function (e, res) {
if (e) { return void cb(e); }
var id = res[0];
if (typeof(id) !== 'string') {
return void cb('INVALID_ID');
}
cb(void 0, id);
});
};
exp.ownedUploadComplete = function (id, cb) {
rpc.send('OWNED_UPLOAD_COMPLETE', id, function (e, res) {
if (e) { return void cb(e); }
var id = res[0];
if (typeof(id) !== 'string') {
@@ -192,13 +214,44 @@ define([
});
};
exp.uploadCancel = function (cb) {
rpc.send('UPLOAD_CANCEL', void 0, function (e) {
exp.uploadCancel = function (size, cb) {
rpc.send('UPLOAD_CANCEL', size, function (e) {
if (e) { return void cb(e); }
cb();
});
};
exp.writeLoginBlock = function (data, cb) {
if (!data) { return void cb('NO_DATA'); }
if (!data.publicKey || !data.signature || !data.ciphertext) {
console.log(data);
return void cb("MISSING_PARAMETERS");
}
rpc.send('WRITE_LOGIN_BLOCK', [
data.publicKey,
data.signature,
data.ciphertext
], function (e) {
cb(e);
});
};
exp.removeLoginBlock = function (data, cb) {
if (!data) { return void cb('NO_DATA'); }
if (!data.publicKey || !data.signature) {
console.log(data);
return void cb("MISSING_PARAMETERS");
}
rpc.send('REMOVE_LOGIN_BLOCK', [
data.publicKey, // publicKey
data.signature, // signature
], function (e) {
cb(e);
});
};
cb(e, exp);
});
};

1024
www/common/proxy-manager.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -17,8 +17,7 @@ define([
'/bower_components/file-saver/FileSaver.min.js',
'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',
'css!/bower_components/components-font-awesome/css/font-awesome.min.css',
], function (
$,
Hyperjson,
@@ -126,7 +125,7 @@ define([
if (newState === STATE.INFINITE_SPINNER || newState === STATE.DELETED) {
state = newState;
} else if (state === STATE.DISCONNECTED && newState !== STATE.INITIALIZING) {
throw new Error("Cannot transition from DISCONNECTED to " + newState);
throw new Error("Cannot transition from DISCONNECTED to " + newState); // FIXME we are getting "DISCONNECTED to READY" on prod
} else if (state !== STATE.READY && newState === STATE.HISTORY_MODE) {
throw new Error("Cannot transition from " + state + " to " + newState);
} else {
@@ -140,6 +139,11 @@ define([
toolbar.initializing();
return;
}
if (text) {
// text is a boolean here. It means we won't try to reconnect
toolbar.failed();
return;
}
toolbar.reconnecting();
});
break;
@@ -171,9 +175,12 @@ define([
}
};
var contentUpdate = function (newContent) {
var oldContent;
var contentUpdate = function (newContent, waitFor) {
if (JSONSortify(newContent) === JSONSortify(oldContent)) { return; }
try {
evContentUpdate.fire(newContent);
evContentUpdate.fire(newContent, waitFor);
setTimeout(function () { oldContent = newContent; });
} catch (e) {
console.log(e.stack);
UI.errorLoadingScreen(e.message);
@@ -192,46 +199,48 @@ define([
cpNfInner.metadataMgr.updateMetadata(meta);
newContent = normalize(newContent);
contentUpdate(newContent);
nThen(function (waitFor) {
contentUpdate(newContent, waitFor);
}).nThen(function () {
if (!readOnly) {
var newContent2NoMeta = normalize(contentGetter());
var newContent2StrNoMeta = JSONSortify(newContent2NoMeta);
var newContentStrNoMeta = JSONSortify(newContent);
if (!readOnly) {
var newContent2NoMeta = normalize(contentGetter());
var newContent2StrNoMeta = JSONSortify(newContent2NoMeta);
var newContentStrNoMeta = JSONSortify(newContent);
if (newContent2StrNoMeta !== newContentStrNoMeta) {
console.error("shjson2 !== shjson");
onLocal();
if (newContent2StrNoMeta !== newContentStrNoMeta) {
console.error("shjson2 !== shjson");
onLocal();
/* pushing back over the wire is necessary, but it can
result in a feedback loop, which we call a browser
fight */
// what changed?
var ops = ChainPad.Diff.diff(newContentStrNoMeta, newContent2StrNoMeta);
// log the changes
console.log(newContentStrNoMeta);
console.log(ops);
var sop = JSON.stringify([ newContentStrNoMeta, ops ]);
/* pushing back over the wire is necessary, but it can
result in a feedback loop, which we call a browser
fight */
// what changed?
var ops = ChainPad.Diff.diff(newContentStrNoMeta, newContent2StrNoMeta);
// log the changes
console.log(newContentStrNoMeta);
console.log(ops);
var sop = JSON.stringify([ newContentStrNoMeta, ops ]);
var fights = window.CryptPad_fights = window.CryptPad_fights || [];
var index = fights.indexOf(sop);
if (index === -1) {
fights.push(sop);
console.log("Found a new type of browser disagreement");
console.log("You can inspect the list in your " +
"console at `REALTIME_MODULE.fights`");
console.log(fights);
} else {
console.log("Encountered a known browser disagreement: " +
"available at `REALTIME_MODULE.fights[%s]`", index);
var fights = window.CryptPad_fights = window.CryptPad_fights || [];
var index = fights.indexOf(sop);
if (index === -1) {
fights.push(sop);
console.log("Found a new type of browser disagreement");
console.log("You can inspect the list in your " +
"console at `REALTIME_MODULE.fights`");
console.log(fights);
} else {
console.log("Encountered a known browser disagreement: " +
"available at `REALTIME_MODULE.fights[%s]`", index);
}
}
}
}
// Notify only when the content has changed, not when someone has joined/left
if (JSONSortify(newContent) !== JSONSortify(oldContent)) {
common.notify();
}
// Notify only when the content has changed, not when someone has joined/left
if (JSONSortify(newContent) !== JSONSortify(oldContent)) {
common.notify();
}
});
};
var setHistoryMode = function (bool, update) {
@@ -279,66 +288,71 @@ define([
var newContentStr = cpNfInner.chainpad.getUserDoc();
if (state === STATE.DELETED) { return; }
UI.updateLoadingProgress({ state: -1 }, false);
var newPad = false;
if (newContentStr === '') { newPad = true; }
if (!newPad) {
var newContent = JSON.parse(newContentStr);
cpNfInner.metadataMgr.updateMetadata(extractMetadata(newContent));
newContent = normalize(newContent);
contentUpdate(newContent);
} else {
if (!cpNfInner.metadataMgr.getPrivateData().isNewFile) {
// We're getting 'new pad' but there is an existing file
// We don't know exactly why this can happen but under no circumstances
// should we overwrite the content, so lets just try again.
console.log("userDoc is '' but this is not a new pad.");
console.log("Either this is an empty document which has not been touched");
console.log("Or else something is terribly wrong, reloading.");
Feedback.send("NON_EMPTY_NEWDOC");
setTimeout(function () { common.gotoURL(); }, 1000);
return;
// contentUpdate may be async so we need an nthen here
nThen(function (waitFor) {
if (!newPad) {
var newContent = JSON.parse(newContentStr);
cpNfInner.metadataMgr.updateMetadata(extractMetadata(newContent));
newContent = normalize(newContent);
contentUpdate(newContent, waitFor);
} else {
if (!cpNfInner.metadataMgr.getPrivateData().isNewFile) {
// We're getting 'new pad' but there is an existing file
// We don't know exactly why this can happen but under no circumstances
// should we overwrite the content, so lets just try again.
console.log("userDoc is '' but this is not a new pad.");
console.log("Either this is an empty document which has not been touched");
console.log("Or else something is terribly wrong, reloading.");
Feedback.send("NON_EMPTY_NEWDOC");
setTimeout(function () { common.gotoURL(); }, 1000);
return;
}
console.log('updating title');
title.updateTitle(title.defaultTitle);
evOnDefaultContentNeeded.fire();
}
console.log('updating title');
title.updateTitle(title.defaultTitle);
evOnDefaultContentNeeded.fire();
}
stateChange(STATE.READY);
firstConnection = false;
if (!readOnly) { onLocal(); }
evOnReady.fire(newPad);
}).nThen(function () {
stateChange(STATE.READY);
firstConnection = false;
if (!readOnly) { onLocal(); }
evOnReady.fire(newPad);
UI.removeLoadingScreen(emitResize);
UI.removeLoadingScreen(emitResize);
var privateDat = cpNfInner.metadataMgr.getPrivateData();
var hash = privateDat.availableHashes.editHash ||
privateDat.availableHashes.viewHash;
var href = privateDat.pathname + '#' + hash;
if (AppConfig.textAnalyzer && textContentGetter) {
var channelId = Hash.hrefToHexChannelId(href);
AppConfig.textAnalyzer(textContentGetter, channelId);
}
if (options.thumbnail && privateDat.thumbnails) {
if (hash) {
options.thumbnail.href = href;
options.thumbnail.getContent = function () {
if (!cpNfInner.chainpad) { return; }
return cpNfInner.chainpad.getUserDoc();
};
Thumb.initPadThumbnails(common, options.thumbnail);
var privateDat = cpNfInner.metadataMgr.getPrivateData();
var hash = privateDat.availableHashes.editHash ||
privateDat.availableHashes.viewHash;
var href = privateDat.pathname + '#' + hash;
if (AppConfig.textAnalyzer && textContentGetter) {
AppConfig.textAnalyzer(textContentGetter, privateDat.channel);
}
}
var skipTemp = Util.find(privateDat, ['settings', 'general', 'creation', 'noTemplate']);
var skipCreation = Util.find(privateDat, ['settings', 'general', 'creation', 'skip']);
if (newPad && (!AppConfig.displayCreationScreen || (!skipTemp && skipCreation))) {
common.openTemplatePicker();
}
if (options.thumbnail && privateDat.thumbnails) {
if (hash) {
options.thumbnail.href = href;
options.thumbnail.getContent = function () {
if (!cpNfInner.chainpad) { return; }
return cpNfInner.chainpad.getUserDoc();
};
Thumb.initPadThumbnails(common, options.thumbnail);
}
}
var skipTemp = Util.find(privateDat, ['settings', 'general', 'creation', 'noTemplate']);
var skipCreation = Util.find(privateDat, ['settings', 'general', 'creation', 'skip']);
if (newPad && (!AppConfig.displayCreationScreen || (!skipTemp && skipCreation))) {
common.openTemplatePicker();
}
});
};
var onConnectionChange = function (info) {
if (state === STATE.DELETED) { return; }
stateChange(info.state ? STATE.INITIALIZING : STATE.DISCONNECTED);
stateChange(info.state ? STATE.INITIALIZING : STATE.DISCONNECTED, info.permanent);
/*if (info.state) {
UI.findOKButton().click();
} else {
@@ -379,13 +393,19 @@ define([
common.createButton('import', true, options, function (c, f) {
if (async) {
fi(c, f, function (content) {
contentUpdate(content);
onLocal();
nThen(function (waitFor) {
contentUpdate(content, waitFor);
}).nThen(function () {
onLocal();
});
});
return;
}
contentUpdate(fi(c, f));
onLocal();
nThen(function (waitFor) {
contentUpdate(fi(c, f), waitFor);
}).nThen(function () {
onLocal();
});
})
);
};
@@ -432,6 +452,9 @@ define([
nThen(function (waitFor) {
UI.addLoadingScreen();
SFCommon.create(waitFor(function (c) { common = c; }));
UI.updateLoadingProgress({
state: 1
}, false);
}).nThen(function (waitFor) {
common.getSframeChannel().onReady(waitFor());
}).nThen(function (waitFor) {
@@ -443,7 +466,7 @@ define([
patchTransformer: options.patchTransformer || ChainPad.SmartJSONTransformer,
// cryptpad debug logging (default is 1)
// logLevel: 2,
logLevel: 1,
validateContent: options.validateContent || function (content) {
try {
JSON.parse(content);
@@ -457,10 +480,17 @@ define([
},
onRemote: onRemote,
onLocal: onLocal,
onInit: function () { stateChange(STATE.INITIALIZING); },
onInit: function () {
UI.updateLoadingProgress({
state: 2,
progress: 0.1
}, false);
stateChange(STATE.INITIALIZING);
},
onReady: function () { evStart.reg(onReady); },
onConnectionChange: onConnectionChange,
onError: onError
onError: onError,
updateLoadingProgress: UI.updateLoadingProgress
});
var privReady = Util.once(waitFor());
@@ -555,7 +585,9 @@ define([
onRemote: onRemote,
setHistory: setHistoryMode,
applyVal: function (val) {
contentUpdate(JSON.parse(val) || ["BODY",{},[]]);
contentUpdate(JSON.parse(val) || ["BODY",{},[]], function (h) {
return h;
});
},
$toolbar: $(toolbarContainer)
};
@@ -572,6 +604,9 @@ define([
toolbar.$rightside.append($templateButton);
}
var $importTemplateButton = common.createButton('importtemplate', true);
toolbar.$drawer.append($importTemplateButton);
/* add a forget button */
toolbar.$rightside.append(common.createButton('forget', true, {}, function (err) {
if (err) { return; }

View File

@@ -41,10 +41,11 @@ define([
var patchTransformer = config.patchTransformer;
var validateContent = config.validateContent;
var avgSyncMilliseconds = config.avgSyncMilliseconds;
var logLevel = typeof(config.logLevel) !== 'undefined'? config.logLevel : 2;
var logLevel = typeof(config.logLevel) !== 'undefined'? config.logLevel : 1;
var readOnly = config.readOnly || false;
var sframeChan = config.sframeChan;
var metadataMgr = config.metadataMgr;
var updateLoadingProgress = config.updateLoadingProgress;
config = undefined;
var chainpad = ChainPad.create({
@@ -64,6 +65,7 @@ define([
var myID;
var isReady = false;
var isHistory = 1;
var evConnected = Util.mkEvent(true);
var evInfiniteSpinner = Util.mkEvent(true);
@@ -79,10 +81,12 @@ define([
evInfiniteSpinner.fire();
}, 2000);
sframeChan.on('EV_RT_DISCONNECT', function () {
sframeChan.on('EV_RT_DISCONNECT', function (isPermanent) {
isReady = false;
chainpad.abort();
onConnectionChange({ state: false });
// Permanent flag is here to choose if we wnat to display
// "reconnecting" or "disconnected" in the toolbar state
onConnectionChange({ state: false, permanent: isPermanent });
});
sframeChan.on('EV_RT_ERROR', function (err) {
isReady = false;
@@ -112,11 +116,19 @@ define([
onLocal(true); // should be onBeforeMessage
}
chainpad.message(content);
if (isHistory && updateLoadingProgress) {
updateLoadingProgress({
state: 2,
progress: isHistory
}, false);
isHistory++;
}
cb('OK');
});
sframeChan.on('EV_RT_READY', function () {
if (isReady) { return; }
isReady = true;
isHistory = false;
chainpad.start();
setMyID({ myID: myID });
onReady({ realtime: chainpad });

View File

@@ -39,9 +39,11 @@ define([], function () {
});
// shim between chainpad and netflux
var msgIn = function (msg) {
var msgIn = function (peer, msg) {
try {
var decryptedMsg = Crypto.decrypt(msg, isNewHash);
var isHk = peer.length !== 32;
var key = isNewHash ? validateKey : false;
var decryptedMsg = Crypto.decrypt(msg, key, isHk);
return decryptedMsg;
} catch (err) {
console.error(err);
@@ -53,7 +55,16 @@ define([], function () {
if (readOnly) { return; }
try {
var cmsg = Crypto.encrypt(msg);
if (msg.indexOf('[4') === 0) { cmsg = 'cp|' + cmsg; }
if (msg.indexOf('[4') === 0) {
var id = '';
if (window.nacl) {
var hash = window.nacl.hash(window.nacl.util.decodeUTF8(msg));
id = window.nacl.util.encodeBase64(hash.slice(0, 8)) + '|';
} else {
console.log("Checkpoint sent without an ID. Nacl is missing.");
}
cmsg = 'cp|' + id + cmsg;
}
return cmsg;
} catch (err) {
console.log(msg);
@@ -67,8 +78,11 @@ define([], function () {
padRpc.sendPadMsg(msg, cb);
});
var onMessage = function(msg) {
var message = msgIn(msg);
var onMessage = function(msgObj) {
if (msgObj.validateKey && !validateKey) {
validateKey = msgObj.validateKey;
}
var message = msgIn(msgObj.user, msgObj.msg);
verbose(message);
@@ -98,8 +112,12 @@ define([], function () {
}
};
padRpc.onDisconnectEvent.reg(function () {
sframeChan.event('EV_RT_DISCONNECT');
padRpc.onDisconnectEvent.reg(function (permanent) {
sframeChan.event('EV_RT_DISCONNECT', permanent);
});
padRpc.onConnectEvent.reg(function (data) {
onOpen(data);
});
padRpc.onErrorEvent.reg(function (err) {
@@ -114,8 +132,6 @@ define([], function () {
owners: owners,
password: password,
expire: expire
}, function(data) {
onOpen(data);
});
};

View File

@@ -21,16 +21,18 @@ define([
var chan = {};
// Send a query. channel.query('Q_SOMETHING', { args: "whatever" }, function (reply) { ... });
chan.query = function (q, content, cb) {
chan.query = function (q, content, cb, opts) {
if (!otherWindow) { throw new Error('not yet initialized'); }
if (!SFrameProtocol[q]) {
throw new Error('please only make queries are defined in sframe-protocol.js');
}
opts = opts || {};
var txid = mkTxid();
var to = opts.timeout || 30000;
var timeout = setTimeout(function () {
delete queries[txid];
console.log("Timeout making query " + q);
}, 30000);
}, to);
queries[txid] = function (data, msg) {
clearTimeout(timeout);
delete queries[txid];

View File

@@ -329,14 +329,11 @@ define([
dropArea: $('.CodeMirror'),
body: $('body'),
onUploaded: function (ev, data) {
//var cursor = editor.getCursor();
//var cleanName = data.name.replace(/[\[\]]/g, '');
//var text = '!['+cleanName+']('+data.url+')';
var parsed = Hash.parsePadUrl(data.url);
var hexFileName = Util.base64ToHex(parsed.hashData.channel);
var src = '/blob/' + hexFileName.slice(0,2) + '/' + hexFileName;
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' +
parsed.hashData.key + '"></media-tag>';
var secret = Hash.getSecrets('file', parsed.hash, data.password);
var src = Hash.getBlobPathFromHex(secret.channel);
var key = Hash.encodeBase64(secret.keys.cryptKey);
var mt = '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '"></media-tag>';
editor.replaceSelection(mt);
}
};

View File

@@ -3,11 +3,13 @@ define([
'/file/file-crypto.js',
'/common/common-thumbnail.js',
'/common/common-interface.js',
'/common/common-ui-elements.js',
'/common/common-util.js',
'/common/hyperscript.js',
'/customize/messages.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
], function ($, FileCrypto, Thumb, UI, Util, Messages) {
], function ($, FileCrypto, Thumb, UI, UIElements, Util, h, Messages) {
var Nacl = window.nacl;
var module = {};
@@ -26,6 +28,7 @@ define([
module.create = function (common, config) {
var File = {};
var origin = common.getMetadataMgr().getPrivateData().origin;
var queue = File.queue = {
queue: [],
@@ -53,6 +56,7 @@ define([
data.name = file.metadata.name;
data.url = href;
data.password = file.password;
if (file.metadata.type.slice(0,6) === 'image/') {
data.mediatag = true;
}
@@ -219,29 +223,89 @@ define([
queue.next();
};
// 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) {
// Get the upload options
var modalState = {
owned: true,
store: true
};
var fileUploadModal = function (file, cb) {
var extIdx = file.name.lastIndexOf('.');
var name = extIdx !== -1 ? file.name.slice(0,extIdx) : file.name;
var ext = extIdx !== -1 ? file.name.slice(extIdx) : "";
var msg = Messages._getKey('upload_rename', [
Util.fixHTML(file.name),
Util.fixHTML(ext)
var createHelper = function (href, text) {
var q = h('a.fa.fa-question-circle', {
style: 'text-decoration: none !important;',
title: text,
href: origin + href,
target: "_blank",
'data-tippy-placement': "right"
});
return q;
};
var privateData = common.getMetadataMgr().getPrivateData();
var autoStore = Util.find(privateData, ['settings', 'general', 'autostore']) || 0;
var initialState = modalState.owned || modalState.store;
var initialDisabled = modalState.owned ? { disabled: true } : {};
var manualStore = autoStore === 1 ? undefined :
UI.createCheckbox('cp-upload-store', Messages.autostore_forceSave, initialState, {
input: initialDisabled
});
// Ask for name, password and owner
var content = h('div', [
h('h4', Messages.upload_modal_title),
UIElements.setHTML(h('label', {for: 'cp-upload-name'}),
Messages._getKey('upload_modal_filename', [ext])),
h('input#cp-upload-name', {type: 'text', placeholder: name}),
h('label', {for: 'cp-upload-password'}, Messages.creation_passwordValue),
UI.passwordInput({id: 'cp-upload-password'}),
h('span', {
style: 'display:flex;align-items:center;justify-content:space-between'
}, [
UI.createCheckbox('cp-upload-owned', Messages.upload_modal_owner, modalState.owned),
createHelper('/faq.html#keywords-owned', Messages.creation_owned1)
]),
manualStore
]);
UI.prompt(msg, name, function (newName) {
if (newName === null) {
showNamePrompt = false;
return void cb (file.name);
$(content).find('#cp-upload-owned').on('change', function () {
var val = $(content).find('#cp-upload-owned').is(':checked');
if (val) {
$(content).find('#cp-upload-store').prop('checked', true).prop('disabled', true);
} else {
$(content).find('#cp-upload-store').prop('disabled', false);
}
if (!newName || !newName.trim()) { return void cb (file.name); }
});
UI.confirm(content, function (yes) {
if (!yes) { return void cb(); }
// Get the values
var newName = $(content).find('#cp-upload-name').val();
var password = $(content).find('#cp-upload-password').val() || undefined;
var owned = $(content).find('#cp-upload-owned').is(':checked');
var forceSave = owned || $(content).find('#cp-upload-store').is(':checked');
modalState.owned = owned;
modalState.store = forceSave;
// Add extension to the name if needed
if (!newName || !newName.trim()) { newName = file.name; }
var newExtIdx = newName.lastIndexOf('.');
var newExt = newExtIdx !== -1 ? newName.slice(newExtIdx) : "";
if (newExt !== ext) { newName += ext; }
cb(newName);
}, {cancel: Messages.doNotAskAgain}, true);
cb({
name: newName,
password: password,
owned: owned,
forceSave: forceSave
});
});
};
var handleFileState = {
queue: [],
inProgress: false
@@ -253,17 +317,25 @@ define([
var thumb;
var file_arraybuffer;
var name = file.name;
var finish = function () {
var metadata = {
name: name,
type: file.type,
};
if (thumb) { metadata.thumbnail = thumb; }
queue.push({
blob: file_arraybuffer,
metadata: metadata,
dropEvent: e
});
var password;
var owned = true;
var forceSave;
var finish = function (abort) {
if (!abort) {
var metadata = {
name: name,
type: file.type,
};
if (thumb) { metadata.thumbnail = thumb; }
queue.push({
blob: file_arraybuffer,
metadata: metadata,
password: password,
owned: owned,
forceSave: forceSave,
dropEvent: e
});
}
handleFileState.inProgress = false;
if (handleFileState.queue.length) {
var next = handleFileState.queue.shift();
@@ -271,9 +343,17 @@ define([
}
};
var getName = function () {
if (!showNamePrompt) { return void finish(); }
promptName(file, function (newName) {
name = newName;
// If "noStore", it means we don't want to store this file in our drive (avatar)
// In this case, we don't want a password or a filename, and we own the file
if (config.noStore) { return void finish(); }
// Otherwise, ask for password, name and ownership
fileUploadModal(file, function (obj) {
if (!obj) { return void finish(true); }
name = obj.name;
password = obj.password;
owned = obj.owned;
forceSave = obj.forceSave;
finish();
});
};
@@ -340,6 +420,8 @@ define([
var editor = config.ckeditor;
editor.document.on('drop', function (ev) {
var dropped = ev.data.$.dataTransfer.files;
editor.document.focus();
if (!dropped || !dropped.length) { return; }
onFileDrop(dropped, ev);
ev.data.preventDefault(true);
});

View File

@@ -1,26 +1,36 @@
define([
'jquery',
'/common/common-interface.js',
'/bower_components/nthen/index.js',
//'/bower_components/chainpad-json-validator/json-ot.js',
'/bower_components/chainpad/chainpad.dist.js',
], function ($, UI, ChainPad /* JsonOT */) {
], function ($, UI, nThen, ChainPad /* JsonOT */) {
//var ChainPad = window.ChainPad;
var History = {};
var getStates = function (rt) {
var states = [];
var b = rt.getAuthBlock();
if (b) { states.unshift(b); }
while (b.getParent()) {
b = b.getParent();
states.unshift(b);
}
return states;
};
History.create = function (common, config) {
if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");}
if (History.loading) { return void console.error("History is already being loaded..."); }
History.loading = true;
var $toolbar = config.$toolbar;
var loadHistory = function (config, common, cb) {
var createRealtime = function () {
if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) {
throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory");
}
var getStates = function (rt) {
var states = [];
var b = rt.getAuthBlock();
if (b) { states.unshift(b); }
while (b.getParent()) {
b = b.getParent();
states.unshift(b);
}
return states;
};
var createRealtime = function (config) {
return ChainPad.create({
userName: 'history',
validateContent: function (content) {
@@ -33,36 +43,45 @@ define([
}
},
initialState: '',
//patchTransformer: ChainPad.NaiveJSONTransformer,
//logLevel: 0,
//transformFunction: JsonOT.validate,
logLevel: config.debug ? 2 : 0,
noPrune: true
});
};
var realtime = createRealtime();
History.readOnly = common.getMetadataMgr().getPrivateData().readOnly;
var loadFullHistory = function (config, common, cb) {
var realtime = createRealtime(config);
common.getFullHistory(realtime, function () {
cb(null, realtime);
});
};
loadFullHistory = loadFullHistory;
/*var to = window.setTimeout(function () {
cb('[GET_FULL_HISTORY_TIMEOUT]');
}, 30000);*/
var fillChainPad = function (realtime, messages) {
messages.forEach(function (m) {
realtime.message(m);
});
};
common.getFullHistory(realtime, function () {
//window.clearTimeout(to);
cb(null, realtime);
});
};
var allMessages = [];
var lastKnownHash;
var isComplete = false;
var loadMoreHistory = function (config, common, cb) {
if (isComplete) { return void cb ('EFULL'); }
var realtime = createRealtime(config);
var sframeChan = common.getSframeChannel();
History.create = function (common, config) {
if (!config.$toolbar) { return void console.error("config.$toolbar is undefined");}
if (History.loading) { return void console.error("History is already being loaded..."); }
History.loading = true;
var $toolbar = config.$toolbar;
if (!config.applyVal || !config.setHistory || !config.onLocal || !config.onRemote) {
throw new Error("Missing config element: applyVal, onLocal, onRemote, setHistory");
}
sframeChan.query('Q_GET_HISTORY_RANGE', {
lastKnownHash: lastKnownHash
}, function (err, data) {
if (err) { return void console.error(err); }
if (!Array.isArray(data.messages)) { return void console.error('Not an array!'); }
lastKnownHash = data.lastKnownHash;
isComplete = data.isFull;
Array.prototype.unshift.apply(allMessages, data.messages); // Destructive concat
fillChainPad(realtime, allMessages);
cb (null, realtime, data.isFull);
});
};
// config.setHistory(bool, bool)
// - bool1: history value
@@ -84,21 +103,20 @@ define([
};
config.setHistory(true);
var onReady = function () { };
var Messages = common.Messages;
var realtime;
var states = [];
var c = states.length - 1;
var c = 0;//states.length - 1;
var $hist = $toolbar.find('.cp-toolbar-history');
var $left = $toolbar.find('.cp-toolbar-leftside');
var $right = $toolbar.find('.cp-toolbar-rightside');
var $cke = $toolbar.find('.cke_toolbox_main');
$hist.html('').show();
$hist.html('').css('display', 'flex');
$left.hide();
$right.hide();
$cke.hide();
@@ -107,29 +125,78 @@ define([
var onUpdate;
var update = function () {
var update = function (newRt) {
realtime = newRt;
if (!realtime) { return []; }
states = getStates(realtime);
if (typeof onUpdate === "function") { onUpdate(); }
return states;
};
var $loadMore, $version, get;
// Get the content of the selected version, and change the version number
var get = function (i) {
var loading = false;
var loadMore = function (cb) {
if (loading) { return; }
loading = true;
$loadMore.removeClass('fa fa-ellipsis-h')
.append($('<span>', {'class': 'fa fa-refresh fa-spin fa-3x fa-fw'}));
loadMoreHistory(config, common, function (err, newRt, isFull) {
if (err === 'EFULL') {
$loadMore.off('click').hide();
get(c);
$version.show();
return;
}
loading = false;
if (err) { return void console.error(err); }
update(newRt);
$loadMore.addClass('fa fa-ellipsis-h').html('');
get(c);
if (isFull) {
$loadMore.off('click').hide();
$version.show();
}
if (cb) { cb(); }
});
};
get = function (i) {
i = parseInt(i);
if (isNaN(i)) { return; }
if (i < 0) { i = 0; }
if (i > states.length - 1) { i = states.length - 1; }
var val = states[i].getContent().doc;
if (i > 0) { i = 0; }
if (i < -(states.length - 2)) { i = -(states.length - 2); }
if (i <= -(states.length - 11)) {
loadMore();
}
var idx = states.length - 1 + i;
var val = states[idx].getContent().doc;
c = i;
if (typeof onUpdate === "function") { onUpdate(); }
$hist.find('.cp-toolbar-history-next, .cp-toolbar-history-previous').css('visibility', '');
if (c === states.length - 1) { $hist.find('.cp-toolbar-history-next').css('visibility', 'hidden'); }
if (c === 0) { $hist.find('.cp-toolbar-history-previous').css('visibility', 'hidden'); }
$hist.find('.cp-toolbar-history-next, .cp-toolbar-history-previous, ' +
'.cp-toolbar-history-fast-next, .cp-toolbar-history-fast-previous')
.css('visibility', '');
if (c === -(states.length-1)) {
$hist.find('.cp-toolbar-history-previous').css('visibility', 'hidden');
$hist.find('.cp-toolbar-history-fast-previous').css('visibility', 'hidden');
}
if (c === 0) {
$hist.find('.cp-toolbar-history-next').css('visibility', 'hidden');
$hist.find('.cp-toolbar-history-fast-next').css('visibility', 'hidden');
}
var $pos = $hist.find('.cp-toolbar-history-pos');
var p = 100 * (1 - (-c / (states.length-2)));
$pos.css('margin-left', p+'%');
// Display the version when the full history is loaded
// Note: the first version is always empty and probably can't be displayed, so
// we can consider we have only states.length - 1 versions
$version.text(idx + ' / ' + (states.length-1));
if (config.debug) {
console.log(states[i]);
var ops = states[i] && states[i].getPatch() && states[i].getPatch().operations;
console.log(states[idx]);
var ops = states[idx] && states[idx].getPatch() && states[idx].getPatch().operations;
if (Array.isArray(ops)) {
ops.forEach(function (op) { console.log(op); });
}
@@ -148,6 +215,17 @@ define([
// Create the history toolbar
var display = function () {
$hist.html('');
var $rev = $('<button>', {
'class':'cp-toolbar-history-revert buttonSuccess fa fa-check-circle-o',
title: Messages.history_restoreTitle
}).appendTo($hist);//.text(Messages.history_restore);
if (History.readOnly) { $rev.css('visibility', 'hidden'); }
$('<span>', {'class': 'cp-history-filler'}).appendTo($hist);
var $fastPrev = $('<button>', {
'class': 'cp-toolbar-history-fast-previous fa fa-fast-backward buttonPrimary',
title: Messages.history_prev
}).appendTo($hist);
var $prev =$('<button>', {
'class': 'cp-toolbar-history-previous fa fa-step-backward buttonPrimary',
title: Messages.history_prev
@@ -157,58 +235,73 @@ define([
'class': 'cp-toolbar-history-next fa fa-step-forward buttonPrimary',
title: Messages.history_next
}).appendTo($hist);
$('<label>').text(Messages.history_version).appendTo($nav);
var $cur = $('<input>', {
'class' : 'cp-toolbar-history-goto-input',
'type' : 'number',
'min' : '1',
'max' : states.length
}).val(c + 1).appendTo($nav).mousedown(function (e) {
// stopPropagation because the event would be cancelled by the dropdown menus
e.stopPropagation();
});
var $label2 = $('<label>').text(' / '+ states.length).appendTo($nav);
$('<br>').appendTo($nav);
var $fastNext = $('<button>', {
'class': 'cp-toolbar-history-fast-next fa fa-fast-forward buttonPrimary',
title: Messages.history_next
}).appendTo($hist);
$('<span>', {'class': 'cp-history-filler'}).appendTo($hist);
var $close = $('<button>', {
'class':'cp-toolbar-history-close',
'class':'cp-toolbar-history-close fa fa-window-close',
title: Messages.history_closeTitle
}).text(Messages.history_closeTitle).appendTo($nav);
var $rev = $('<button>', {
'class':'cp-toolbar-history-revert buttonSuccess',
title: Messages.history_restoreTitle
}).text(Messages.history_restore).appendTo($nav);
if (History.readOnly) { $rev.hide(); }
}).appendTo($hist);
var $bar = $('<div>', {'class': 'cp-toolbar-history-bar'}).appendTo($nav);
var $container = $('<div>', {'class':'cp-toolbar-history-pos-container'}).appendTo($bar);
$('<div>', {'class': 'cp-toolbar-history-pos'}).appendTo($container);
$version = $('<span>', {
'class': 'cp-toolbar-history-version'
}).prependTo($bar).hide();
$loadMore = $('<button>', {
'class':'cp-toolbar-history-loadmore fa fa-ellipsis-h',
title: Messages.history_loadMore
}).click(function () {
loadMore(function () {
get(c);
});
}).prependTo($container);
// Load a version when clicking on the bar
$container.click(function (e) {
e.stopPropagation();
if (!$(e.target).is('.cp-toolbar-history-pos-container')) { return; }
var p = e.offsetX / $container.width();
var v = -Math.round((states.length - 1) * (1 - p));
render(get(v));
});
onUpdate = function () {
$cur.attr('max', states.length);
$cur.val(c+1);
$label2.text(' / ' + states.length);
// Called when a new version is loaded
};
var onKeyDown, onKeyUp;
var close = function () {
$hist.hide();
$left.show();
$right.show();
$cke.show();
$(window).trigger('resize');
$(window).off('keydown', onKeyDown);
$(window).off('keyup', onKeyUp);
};
// Buttons actions
// Version buttons
$prev.click(function () { render(getPrevious()); });
$next.click(function () { render(getNext()); });
$cur.keydown(function (e) {
$fastPrev.click(function () { render(getPrevious(10)); });
$fastNext.click(function () { render(getNext(10)); });
onKeyDown = function (e) {
var p = function () { e.preventDefault(); };
if (e.which === 13) { p(); return render( get($cur.val() - 1) ); } // Enter
if ([37, 40].indexOf(e.which) >= 0) { p(); return render(getPrevious()); } // Left
if ([38, 39].indexOf(e.which) >= 0) { p(); return render(getNext()); } // Right
if (e.which === 33) { p(); return render(getNext(10)); } // PageUp
if (e.which === 34) { p(); return render(getPrevious(10)); } // PageUp
if (e.which === 27) { p(); $close.click(); }
}).keyup(function (e) { e.stopPropagation(); }).focus();
$cur.on('change', function () {
render( get($cur.val() - 1) );
});
};
onKeyUp = function (e) { e.stopPropagation(); };
$(window).on('keydown', onKeyDown).on('keyup', onKeyUp).focus();
// Close & restore buttons
$close.click(function () {
states = [];
close();
@@ -229,14 +322,17 @@ define([
};
// Load all the history messages into a new chainpad object
loadHistory(config, common, function (err, newRt) {
loadMoreHistory(config, common, function (err, newRt, isFull) {
History.readOnly = common.getMetadataMgr().getPrivateData().readOnly;
History.loading = false;
if (err) { throw new Error(err); }
realtime = newRt;
update();
update(newRt);
c = states.length - 1;
display();
onReady();
if (isFull) {
$loadMore.off('click').hide();
$version.show();
}
});
};

View File

@@ -24,6 +24,8 @@ define([
var Utils = {};
var AppConfig;
var Test;
var password;
var initialPathInDrive;
nThen(function (waitFor) {
// Load #2, the loading screen is up so grab whatever you need...
@@ -88,7 +90,16 @@ define([
SFrameChannel.create($('#sbox-iframe')[0].contentWindow, waitFor(function (sfc) {
sframeChan = sfc;
}), false, { cache: cache, localStore: localStore, language: Cryptpad.getLanguage() });
Cryptpad.ready(waitFor(), {
Cryptpad.loading.onDriveEvent.reg(function (data) {
if (sframeChan) { sframeChan.event('EV_LOADING_INFO', data); }
});
Cryptpad.ready(waitFor(function () {
if (sframeChan) {
sframeChan.event('EV_LOADING_INFO', {
state: -1
});
}
}), {
messenger: cfg.messaging,
driveEvents: cfg.driveEvents
});
@@ -113,6 +124,7 @@ define([
if (cfg.getSecrets) {
var w = waitFor();
// No password for drive, profile and todo
cfg.getSecrets(Cryptpad, Utils, waitFor(function (err, s) {
secret = s;
Cryptpad.getShareHashes(secret, function (err, h) {
@@ -121,19 +133,83 @@ define([
});
}));
} else {
secret = Utils.Hash.getSecrets();
if (!secret.channel) {
// New pad: create a new random channel id
secret.channel = Utils.Hash.createChannelId();
var parsed = Utils.Hash.parsePadUrl(window.location.href);
var todo = function () {
secret = Utils.Hash.getSecrets(parsed.type, void 0, password);
Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; }));
};
// Prompt the password here if we have a hash containing /p/
// or get it from the pad attributes
var needPassword = parsed.hashData && parsed.hashData.password;
if (needPassword) {
// Check if we have a password, and check if it is correct (file exists).
// It we don't have a correct password, display the password prompt.
// Maybe the file has been deleted from the server or the password has been changed.
Cryptpad.getPadAttribute('password', waitFor(function (err, val) {
var askPassword = function (wrongPasswordStored) {
// Ask for the password and check if the pad exists
// If the pad doesn't exist, it means the password isn't correct
// or the pad has been deleted
var correctPassword = waitFor();
sframeChan.on('Q_PAD_PASSWORD_VALUE', function (data, cb) {
password = data;
var next = function (e, isNew) {
if (Boolean(isNew)) {
// Ask again in the inner iframe
// We should receive a new Q_PAD_PASSWORD_VALUE
cb(false);
} else {
todo();
if (wrongPasswordStored) {
// Store the correct password
Cryptpad.setPadAttribute('password', password, function () {
correctPassword();
}, parsed.getUrl());
} else {
correctPassword();
}
cb(true);
}
};
if (parsed.type === "file") {
// `isNewChannel` doesn't work for files (not a channel)
// `getFileSize` is not adapted to channels because of metadata
Cryptpad.getFileSize(window.location.href, password, function (e, size) {
next(e, size === 0);
});
return;
}
// Not a file, so we can use `isNewChannel`
Cryptpad.isNewChannel(window.location.href, password, next);
});
sframeChan.event("EV_PAD_PASSWORD");
};
if (val) {
password = val;
Cryptpad.getFileSize(window.location.href, password, waitFor(function (e, size) {
if (size !== 0) {
return void todo();
}
// Wrong password or deleted file?
askPassword(true);
}));
} else {
askPassword();
}
}), parsed.getUrl());
return;
}
Cryptpad.getShareHashes(secret, waitFor(function (err, h) { hashes = h; }));
// If no password, continue...
todo();
}
}).nThen(function (waitFor) {
// Check if the pad exists on server
if (!window.location.hash) { isNewFile = true; return; }
if (realtime) {
Cryptpad.isNewChannel(window.location.href, waitFor(function (e, isNew) {
Cryptpad.isNewChannel(window.location.href, password, waitFor(function (e, isNew) {
if (e) { return console.error(e); }
isNewFile = Boolean(isNew);
}));
@@ -188,8 +264,14 @@ define([
},
isNewFile: isNewFile,
isDeleted: isNewFile && window.location.hash.length > 0,
forceCreationScreen: forceCreationScreen
forceCreationScreen: forceCreationScreen,
password: password,
channel: secret.channel,
enableSF: localStorage.CryptPad_SF === "1" // TODO to remove when enabled by default
};
if (window.CryptPad_newSharedFolder) {
additionalPriv.newSharedFolder = window.CryptPad_newSharedFolder;
}
for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; }
if (cfg.addData) {
@@ -208,6 +290,10 @@ define([
Test.registerOuter(sframeChan);
Cryptpad.onNewVersionReconnect.reg(function () {
sframeChan.event("EV_NEW_VERSION");
});
// Put in the following function the RPC queries that should also work in filepicker
var addCommonRpc = function (sframeChan) {
sframeChan.on('Q_ANON_RPC_MESSAGE', function (data, cb) {
@@ -253,10 +339,17 @@ define([
var title = currentTabTitle.replace(/\{title\}/g, currentTitle || 'CryptPad');
document.title = title;
};
sframeChan.on('Q_SET_PAD_TITLE_IN_DRIVE', function (newTitle, cb) {
sframeChan.on('Q_SET_PAD_TITLE_IN_DRIVE', function (newData, cb) {
var newTitle = newData.title || newData.defaultTitle;
currentTitle = newTitle;
setDocumentTitle();
Cryptpad.setPadTitle(newTitle, undefined, undefined, function (err) {
var data = {
password: password,
title: newTitle,
channel: secret.channel,
path: initialPathInDrive // Where to store the pad if we don't have it in our drive
};
Cryptpad.setPadTitle(data, function (err) {
cb(err);
});
});
@@ -265,6 +358,50 @@ define([
setDocumentTitle();
});
Cryptpad.autoStore.onStoreRequest.reg(function (data) {
sframeChan.event("EV_AUTOSTORE_DISPLAY_POPUP", data);
});
sframeChan.on('Q_AUTOSTORE_STORE', function (obj, cb) {
var data = {
password: password,
title: currentTitle,
channel: secret.channel,
path: initialPathInDrive, // Where to store the pad if we don't have it in our drive
forceSave: true
};
Cryptpad.setPadTitle(data, function (err) {
cb(err);
});
});
sframeChan.on('Q_IS_PAD_STORED', function (data, cb) {
Cryptpad.getPadAttribute('title', function (err, data) {
cb (!err && typeof (data) === "string");
});
});
sframeChan.on('Q_IMPORT_MEDIATAG', function (obj, cb) {
var key = obj.key;
var channel = obj.channel;
var hash = Utils.Hash.getFileHashFromKeys({
version: 1,
channel: channel,
keys: {
fileKeyStr: key
}
});
var href = '/file/#' + hash;
var data = {
title: obj.name,
href: href,
channel: channel,
owners: obj.owners,
forceSave: true,
};
Cryptpad.setPadTitle(data, function (err) {
Cryptpad.setPadAttribute('fileType', obj.type, null, href);
cb(err);
});
});
sframeChan.on('Q_SETTINGS_SET_DISPLAY_NAME', function (newName, cb) {
Cryptpad.setDisplayName(newName, function (err) {
@@ -306,6 +443,7 @@ define([
Cryptpad.saveAsTemplate(Cryptget.put, data, cb);
});
// Messaging
sframeChan.on('Q_SEND_FRIEND_REQUEST', function (netfluxId, cb) {
Cryptpad.inviteFromUserlist(netfluxId, cb);
});
@@ -318,6 +456,7 @@ define([
sframeChan.event('EV_FRIEND_REQUEST', data);
});
// History
sframeChan.on('Q_GET_FULL_HISTORY', function (data, cb) {
var crypto = Crypto.createEncryptor(secret.keys);
Cryptpad.getFullHistory({
@@ -325,17 +464,48 @@ define([
validateKey: secret.keys.validateKey
}, function (encryptedMsgs) {
cb(encryptedMsgs.map(function (msg) {
return crypto.decrypt(msg, true);
// The 3rd parameter "true" means we're going to skip signature validation.
// We don't need it since the message is already validated serverside by hk
return crypto.decrypt(msg, true, true);
}));
});
});
sframeChan.on('Q_GET_HISTORY_RANGE', function (data, cb) {
var nSecret = secret;
if (cfg.isDrive) {
var hash = Utils.LocalStore.getUserHash() || Utils.LocalStore.getFSHash();
if (hash) {
nSecret = Utils.Hash.getSecrets('drive', hash);
}
}
var channel = nSecret.channel;
var validate = nSecret.keys.validateKey;
var crypto = Crypto.createEncryptor(nSecret.keys);
Cryptpad.getHistoryRange({
channel: channel,
validateKey: validate,
lastKnownHash: data.lastKnownHash
}, function (data) {
cb({
isFull: data.isFull,
messages: data.messages.map(function (msg) {
// The 3rd parameter "true" means we're going to skip signature validation.
// We don't need it since the message is already validated serverside by hk
return crypto.decrypt(msg, true, true);
}),
lastKnownHash: data.lastKnownHash
});
});
});
// Store
sframeChan.on('Q_GET_PAD_ATTRIBUTE', function (data, cb) {
var href;
if (readOnly && hashes.editHash) {
// If we have a stronger hash, use it for pad attributes
href = window.location.pathname + '#' + hashes.editHash;
}
if (data.href) { href = data.href; }
Cryptpad.getPadAttribute(data.key, function (e, data) {
cb({
error: e,
@@ -349,6 +519,7 @@ define([
// If we have a stronger hash, use it for pad attributes
href = window.location.pathname + '#' + hashes.editHash;
}
if (data.href) { href = data.href; }
Cryptpad.setPadAttribute(data.key, data.value, function (e) {
cb({error:e});
}, href);
@@ -373,6 +544,12 @@ define([
cb();
});
sframeChan.on('Q_IS_ONLY_IN_SHARED_FOLDER', function (data, cb) {
Cryptpad.isOnlyInSharedFolder(secret.channel, function (err, t) {
if (err) { return void cb({error: err}); }
cb(t);
});
});
// Present mode URL
sframeChan.on('Q_PRESENT_URL_GET_VALUE', function (data, cb) {
@@ -429,6 +606,8 @@ define([
// File picker
var FP = {};
var initFilePicker = function (cfg) {
// cfg.hidden means pre-loading the filepicker while keeping it hidden.
// if cfg.hidden is true and the iframe already exists, do nothing
if (!FP.$iframe) {
var config = {};
config.onFilePicked = function (data) {
@@ -447,7 +626,7 @@ define([
};
FP.$iframe = $('<iframe>', {id: 'sbox-filePicker-iframe'}).appendTo($('body'));
FP.picker = FilePicker.create(config);
} else {
} else if (!cfg.hidden) {
FP.$iframe.show();
FP.picker.refresh(cfg);
}
@@ -469,9 +648,9 @@ define([
cb(templates.length > 0);
});
});
var getKey = function (href) {
var getKey = function (href, channel) {
var parsed = Utils.Hash.parsePadUrl(href);
return 'thumbnail-' + parsed.type + '-' + parsed.hashData.channel;
return 'thumbnail-' + parsed.type + '-' + channel;
};
sframeChan.on('Q_CREATE_TEMPLATES', function (type, cb) {
Cryptpad.getSecureFilesList({
@@ -484,12 +663,13 @@ define([
var res = [];
nThen(function (waitFor) {
Object.keys(data).map(function (el) {
var k = getKey(data[el].href);
var k = getKey(data[el].href, data[el].channel);
Utils.LocalStore.getThumbnail(k, waitFor(function (e, thumb) {
res.push({
id: el,
name: data[el].filename || data[el].title || '?',
thumbnail: thumb
thumbnail: thumb,
used: data[el].used || 0
});
}));
});
@@ -513,19 +693,6 @@ define([
}
});
sframeChan.on('Q_TAGS_GET', function (data, cb) {
Cryptpad.getPadTags(data, function (err, data) {
cb({
error: err,
data: data
});
});
});
sframeChan.on('EV_TAGS_SET', function (data) {
Cryptpad.resetTags(data.href, data.tags);
});
sframeChan.on('Q_PIN_GET_USAGE', function (data, cb) {
Cryptpad.isOverPinLimit(function (err, overLimit, data) {
cb({
@@ -546,6 +713,32 @@ define([
Cryptpad.removeOwnedChannel(channel, cb);
});
sframeChan.on('Q_GET_ALL_TAGS', function (data, cb) {
Cryptpad.listAllTags(function (err, tags) {
cb({
error: err,
tags: tags
});
});
});
sframeChan.on('Q_PAD_PASSWORD_CHANGE', function (data, cb) {
var href = data.href || window.location.href;
Cryptpad.changePadPassword(Cryptget, href, data.password, edPublic, cb);
});
sframeChan.on('Q_CHANGE_USER_PASSWORD', function (data, cb) {
Cryptpad.changeUserPassword(Cryptget, edPublic, data, cb);
});
sframeChan.on('Q_WRITE_LOGIN_BLOCK', function (data, cb) {
Cryptpad.writeLoginBlock(data, cb);
});
sframeChan.on('Q_REMOVE_LOGIN_BLOCK', function (data, cb) {
Cryptpad.removeLoginBlock(data, cb);
});
if (cfg.addRpc) {
cfg.addRpc(sframeChan, Cryptpad, Utils);
}
@@ -603,6 +796,24 @@ define([
});
}
// Chrome 68 on Mac contains a bug resulting in the page turning white after a few seconds
try {
if (navigator.platform.toUpperCase().indexOf('MAC') >= 0 &&
!localStorage.CryptPad_chrome68) {
var isChrome = !!window.chrome && !!window.chrome.webstore;
var getChromeVersion = function () {
var raw = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
return raw ? parseInt(raw[2], 10) : false;
};
if (isChrome && getChromeVersion() === 68) {
sframeChan.whenReg('EV_CHROME_68', function () {
sframeChan.event("EV_CHROME_68");
localStorage.CryptPad_chrome68 = "1";
});
}
}
} catch (e) {}
// Join the netflux channel
@@ -630,7 +841,10 @@ define([
isNewHash: isNewHash,
readOnly: readOnly,
crypto: Crypto.createEncryptor(secret.keys),
onConnect: function (wc) {
onConnect: function () {
var href = parsed.getUrl();
// Add friends requests handlers when we have the final href
Cryptpad.messaging.addHandlers(href);
if (window.location.hash && window.location.hash !== '#') {
/*window.location = parsed.getUrl({
present: parsed.hashData.present,
@@ -639,20 +853,36 @@ define([
return;
}
if (readOnly || cfg.noHash) { return; }
replaceHash(Utils.Hash.getEditHashFromKeys(wc, secret.keys));
replaceHash(Utils.Hash.getEditHashFromKeys(secret));
}
};
Object.keys(rtConfig).forEach(function (k) {
cpNfCfg[k] = rtConfig[k];
nThen(function (waitFor) {
if (isNewFile && cfg.owned && !window.location.hash) {
Cryptpad.getMetadata(waitFor(function (err, m) {
cpNfCfg.owners = [m.priv.edPublic];
}));
} else if (isNewFile && !cfg.useCreationScreen && window.location.hash) {
console.log("new file with hash in the address bar in an app without pcs and which requires owners");
sframeChan.onReady(function () {
sframeChan.query("EV_LOADING_ERROR", "DELETED");
});
waitFor.abort();
}
}).nThen(function () {
Object.keys(rtConfig).forEach(function (k) {
cpNfCfg[k] = rtConfig[k];
});
CpNfOuter.start(cpNfCfg);
});
CpNfOuter.start(cpNfCfg);
};
sframeChan.on('Q_CREATE_PAD', function (data, cb) {
if (!isNewFile || rtStarted) { return; }
// Create a new hash
var newHash = Utils.Hash.createRandomHash();
secret = Utils.Hash.getSecrets(parsed.type, newHash);
password = data.password;
var newHash = Utils.Hash.createRandomHash(parsed.type, password);
secret = Utils.Hash.getSecrets(parsed.type, newHash, password);
// Update the hash in the address bar
var ohc = window.onhashchange;
@@ -664,7 +894,7 @@ define([
// Update metadata values and send new metadata inside
parsed = Utils.Hash.parsePadUrl(window.location.href);
defaultTitle = Utils.Hash.getDefaultName(parsed);
hashes = Utils.Hash.getHashes(secret.channel, secret);
hashes = Utils.Hash.getHashes(secret);
readOnly = false;
updateMeta();
@@ -678,7 +908,7 @@ define([
nThen(function(waitFor) {
if (data.templateId) {
if (data.templateId === -1) {
Cryptpad.setInitialPath(['template']);
initialPathInDrive = ['template'];
return;
}
Cryptpad.getPadData(data.templateId, waitFor(function (err, d) {
@@ -690,10 +920,11 @@ define([
// Pass rtConfig to useTemplate because Cryptput will create the file and
// we need to have the owners and expiration time in the first line on the
// server
var cryptputCfg = $.extend(true, {}, rtConfig, {password: password});
Cryptpad.useTemplate(data.template, Cryptget, function () {
startRealtime();
cb();
}, rtConfig);
}, cryptputCfg);
return;
}
// Start realtime outside the iframe and callback
@@ -706,8 +937,8 @@ define([
Utils.Feedback.reportAppUsage();
if (!realtime) { return; }
if (isNewFile && cfg.useCreationScreen) { return; }
if (!realtime && !Test.testing) { return; }
if (isNewFile && cfg.useCreationScreen && !Test.testing) { return; }
//if (isNewFile && Utils.LocalStore.isLoggedIn()
// && AppConfig.displayCreationScreen && cfg.useCreationScreen) { return; }

View File

@@ -42,6 +42,9 @@ define([
exp.updateTitle = function (newTitle, cb) {
cb = cb || $.noop;
if (newTitle === exp.title) { return void cb(); }
if (newTitle === exp.defaultTitle) {
newTitle = "";
}
metadataMgr.updateTitle(newTitle);
titleUpdated = cb;
};
@@ -51,11 +54,16 @@ define([
if ($title) {
$title.find('span.cp-toolbar-title-value').text(md.title || md.defaultTitle);
$title.find('input').val(md.title || md.defaultTitle);
$title.find('input').prop('placeholder', md.defaultTitle);
}
exp.defaultTitle = md.defaultTitle;
exp.title = md.title;
});
metadataMgr.onTitleChange(function (title) {
sframeChan.query('Q_SET_PAD_TITLE_IN_DRIVE', title, function (err) {
metadataMgr.onTitleChange(function (title, defaultTitle) {
sframeChan.query('Q_SET_PAD_TITLE_IN_DRIVE', {
title: title,
defaultTitle: defaultTitle
}, function (err) {
if (err === 'E_OVER_LIMIT') {
return void UI.alert(Messages.pinLimitNotPinned, null, true);
} else if (err) { return; }

View File

@@ -94,6 +94,7 @@ define([
funcs.getPadCreationScreen = callWithCommon(UIElements.getPadCreationScreen);
funcs.createNewPadModal = callWithCommon(UIElements.createNewPadModal);
funcs.onServerError = callWithCommon(UIElements.onServerError);
funcs.importMediaTagMenu = callWithCommon(UIElements.importMediaTagMenu);
// Thumb
funcs.displayThumbnail = callWithCommon(Thumb.displayThumbnail);
@@ -112,22 +113,43 @@ define([
var origin = ctx.metadataMgr.getPrivateData().origin;
return '<script src="' + origin + '/common/media-tag-nacl.min.js"></script>';
};
funcs.getMediatagFromHref = function (href) {
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets('file', parsed.hash);
funcs.getMediatagFromHref = function (obj) {
var data = ctx.metadataMgr.getPrivateData();
var secret;
if (obj) {
secret = Hash.getSecrets('file', obj.hash, obj.password);
} else {
secret = Hash.getSecrets('file', data.availableHashes.fileHash, data.password);
}
if (secret.keys && secret.channel) {
var cryptKey = secret.keys && secret.keys.fileKeyStr;
var hexFileName = Util.base64ToHex(secret.channel);
var key = Hash.encodeBase64(secret.keys && secret.keys.cryptKey);
var hexFileName = secret.channel;
var origin = data.fileHost || data.origin;
var src = origin + Hash.getBlobPathFromHex(hexFileName);
return '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + cryptKey + '">' +
return '<media-tag src="' + src + '" data-crypto-key="cryptpad:' + key + '">' +
'</media-tag>';
}
return;
};
funcs.getFileSize = function (href, cb) {
var channelId = Hash.hrefToHexChannelId(href);
funcs.importMediaTag = function ($mt) {
if (!$mt || !$mt.is('media-tag')) { return; }
var chanStr = $mt.attr('src');
var keyStr = $mt.attr('data-crypto-key');
var channel = chanStr.replace(/\/blob\/[0-9a-f]{2}\//i, '');
var key = keyStr.replace(/cryptpad:/i, '');
var metadata = $mt[0]._mediaObject._blob.metadata;
ctx.sframeChan.query('Q_IMPORT_MEDIATAG', {
channel: channel,
key: key,
name: metadata.name,
type: metadata.type,
owners: metadata.owners
}, function () {
UI.log(Messages.saved);
});
};
funcs.getFileSize = function (channelId, cb) {
funcs.sendAnonRpcMsg("GET_FILE_SIZE", channelId, function (data) {
if (!data) { return void cb("No response"); }
if (data.error) { return void cb(data.error); }
@@ -170,6 +192,7 @@ define([
// Store
funcs.handleNewFile = function (waitFor) {
if (window.__CRYPTPAD_TEST__) { return; }
var priv = ctx.metadataMgr.getPrivateData();
if (priv.isNewFile) {
var c = (priv.settings.general && priv.settings.general.creation) || {};
@@ -196,11 +219,18 @@ define([
ctx.sframeChan.query("Q_CREATE_PAD", {
owned: cfg.owned,
expire: cfg.expire,
password: cfg.password,
template: cfg.template,
templateId: cfg.templateId
}, cb);
};
funcs.isPadStored = function (cb) {
ctx.sframeChan.query("Q_IS_PAD_STORED", null, function (err, obj) {
cb (err || (obj && obj.error), obj);
});
};
funcs.sendAnonRpcMsg = function (msg, content, cb) {
ctx.sframeChan.query('Q_ANON_RPC_MESSAGE', {
msg: msg,
@@ -233,17 +263,20 @@ define([
});
};
funcs.getPadAttribute = function (key, cb) {
// href is optional here: if not provided, we use the href of the current tab
funcs.getPadAttribute = function (key, cb, href) {
ctx.sframeChan.query('Q_GET_PAD_ATTRIBUTE', {
key: key
key: key,
href: href
}, function (err, res) {
cb (err || res.error, res.data);
});
};
funcs.setPadAttribute = function (key, value, cb) {
funcs.setPadAttribute = function (key, value, cb, href) {
cb = cb || $.noop;
ctx.sframeChan.query('Q_SET_PAD_ATTRIBUTE', {
key: key,
href: href,
value: value
}, cb);
};
@@ -343,6 +376,27 @@ define([
window.open(bounceHref);
};
funcs.fixLinks = function (domElement) {
var origin = ctx.metadataMgr.getPrivateData().origin;
$(domElement).find('a[target="_blank"]').click(function (e) {
e.preventDefault();
e.stopPropagation();
var href = $(this).attr('href');
var absolute = /^https?:\/\//i;
if (!absolute.test(href)) {
if (href.slice(0,1) !== '/') { href = '/' + href; }
href = origin + href;
}
funcs.openUnsafeURL(href);
});
$(domElement).find('a[target!="_blank"]').click(function (e) {
e.preventDefault();
e.stopPropagation();
funcs.gotoURL($(this).attr('href'));
});
return $(domElement)[0];
};
funcs.whenRealtimeSyncs = evRealtimeSynced.reg;
var logoutHandlers = [];
@@ -397,19 +451,6 @@ define([
UI.addTooltips();
ctx.sframeChan.on('EV_LOGOUT', function () {
$(window).on('keyup', function (e) {
if (e.keyCode === 27) {
UI.removeLoadingScreen();
}
});
UI.addLoadingScreen({hideTips: true});
UI.errorLoadingScreen(Messages.onLogout, true);
logoutHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
});
ctx.sframeChan.on('Q_INCOMING_FRIEND_REQUEST', function (confirmMsg, cb) {
UI.confirm(confirmMsg, cb, null, true);
});
@@ -419,12 +460,63 @@ define([
UI.log(data.logText);
});
ctx.sframeChan.on("EV_PAD_PASSWORD", function () {
UIElements.displayPasswordPrompt(funcs);
});
ctx.sframeChan.on('EV_LOADING_INFO', function (data) {
UI.updateLoadingProgress(data, true);
});
ctx.sframeChan.on('EV_NEW_VERSION', function () {
var $err = $('<div>').append(Messages.newVersionError);
$err.find('a').click(function () {
funcs.gotoURL();
});
UI.findOKButton().click();
UI.errorLoadingScreen($err, true, true);
});
ctx.sframeChan.on('EV_AUTOSTORE_DISPLAY_POPUP', function (data) {
UIElements.displayStorePadPopup(funcs, data);
});
ctx.metadataMgr.onReady(waitFor());
}).nThen(function () {
try {
var feedback = ctx.metadataMgr.getPrivateData().feedbackAllowed;
Feedback.init(feedback);
} catch (e) { Feedback.init(false); }
ctx.sframeChan.on('EV_LOADING_ERROR', function (err) {
if (err === 'DELETED') {
var msg = Messages.deletedError + '<br>' + Messages.errorRedirectToHome;
UI.errorLoadingScreen(msg, false, function () {
funcs.gotoURL('/drive/');
});
}
});
ctx.sframeChan.on('EV_LOGOUT', function () {
$(window).on('keyup', function (e) {
if (e.keyCode === 27) {
UI.removeLoadingScreen();
}
});
UI.addLoadingScreen({hideTips: true});
var origin = ctx.metadataMgr.getPrivateData().origin;
var href = origin + "/login/";
var onLogoutMsg = Messages._getKey('onLogout', ['<a href="' + href + '" target="_blank">', '</a>']);
UI.errorLoadingScreen(onLogoutMsg, true);
logoutHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
});
ctx.sframeChan.on('EV_CHROME_68', function () {
UI.alert(Messages.chrome68);
});
ctx.sframeChan.ready();
cb(funcs);
});

View File

@@ -74,6 +74,12 @@ define({
// Get the user's pin limit, usage and plan
'Q_PIN_GET_USAGE': true,
// Write/update the login block when the account password is changed
'Q_WRITE_LOGIN_BLOCK': true,
// Remove login blocks
'Q_REMOVE_LOGIN_BLOCK': true,
// Check the pin limit to determine if we can store the pad in the drive or if we should.
// display a warning
'Q_GET_PIN_LIMIT_STATUS': true,
@@ -84,6 +90,7 @@ define({
// Request the full history from the server when the users clicks on the history button.
// Callback is called when the FULL_HISTORY_END message is received in the outside.
'Q_GET_FULL_HISTORY': true,
'Q_GET_HISTORY_RANGE': true,
// When a (full) history message is received from the server.
'EV_RT_HIST_MESSAGE': true,
@@ -107,6 +114,10 @@ define({
'Q_GET_PAD_ATTRIBUTE': true,
'Q_SET_PAD_ATTRIBUTE': true,
// Check if a pad is only in a shared folder or (also) in the main drive.
// This allows us to change the behavior of some buttons (trash icon...)
'Q_IS_ONLY_IN_SHARED_FOLDER': true,
// Open/close the File picker (sent from the iframe to the outside)
'EV_FILE_PICKER_OPEN': true,
'EV_FILE_PICKER_CLOSE': true,
@@ -165,10 +176,6 @@ define({
// Put one entry in the parent sessionStorage
'Q_SESSIONSTORAGE_PUT': true,
// Set and get the tags using the tag prompt button
'Q_TAGS_GET': true,
'EV_TAGS_SET': true,
// Merge the anonymous drive (FS_hash) into the current logged in user's drive, to keep the pads
// in the drive at registration.
'Q_MERGE_ANON_DRIVE': true,
@@ -200,10 +207,10 @@ define({
// Anonymous users can empty their drive and remove FS_hash from localStorage
'EV_BURN_ANON_DRIVE': true,
// Inner drive needs to send command and receive updates from the async store
'Q_DRIVE_USEROBJECT': true,
'Q_DRIVE_GETOBJECT': true,
'Q_DRIVE_RESTORE': true,
// Get the pads deleted from the server by other users to remove them from the drive
'Q_DRIVE_GETDELETED': true,
// Store's userObject need to send log messages to inner to display them in the UI
@@ -218,6 +225,8 @@ define({
// Notifications about connection and disconnection from the network
'EV_NETWORK_DISCONNECT': true,
'EV_NETWORK_RECONNECT': true,
// Reload on new version
'EV_NEW_VERSION': true,
// Pad creation screen: create a pad with the selected attributes (owned, expire)
'Q_CREATE_PAD': true,
@@ -230,4 +239,32 @@ define({
// OnlyOffice: save a new version
'Q_OO_SAVE': true,
// Ask for the pad password when a pad is protected
'EV_PAD_PASSWORD': true,
'Q_PAD_PASSWORD_VALUE': true,
// Change pad password
'Q_PAD_PASSWORD_CHANGE': true,
// Migrate drive to owned drive
'Q_CHANGE_USER_PASSWORD': true,
// Loading events to display in the loading screen
'EV_LOADING_INFO': true,
// Critical error outside the iframe during loading screen
'EV_LOADING_ERROR': true,
// Chrome 68 bug...
'EV_CHROME_68': true,
// Get all existing tags
'Q_GET_ALL_TAGS': true,
// Store pads in the drive
'EV_AUTOSTORE_DISPLAY_POPUP': true,
'Q_AUTOSTORE_STORE': true,
'Q_IS_PAD_STORED': true,
// Import mediatag from a pad
'Q_IMPORT_MEDIATAG': true
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
www/common/tippy/tippy.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -420,7 +420,7 @@ define([
var hashes = metadataMgr.getPrivateData().availableHashes;
var $shareBlock = $('<button>', {
'class': 'fa fa-share-alt cp-toolbar-share-button',
'class': 'fa fa-shhare-alt cp-toolbar-share-button',
title: Messages.shareButton
});
var modal = UIElements.createShareModal({
@@ -449,7 +449,7 @@ define([
var hashes = metadataMgr.getPrivateData().availableHashes;
var $shareBlock = $('<button>', {
'class': 'fa fa-share-alt cp-toolbar-share-button',
'class': 'fa fa-shhare-alt cp-toolbar-share-button',
title: Messages.shareButton
});
var modal = UIElements.createFileShareModal({
@@ -576,9 +576,7 @@ define([
if (Common.isLoggedIn()) { return; }
var pd = config.metadataMgr.getPrivateData();
var o = pd.origin;
var hashes = pd.availableHashes;
var url = pd.origin + pd.pathname + '#' + (hashes.editHash || hashes.viewHash);
var cid = Hash.hrefToHexChannelId(url);
var cid = pd.channel;
Common.sendAnonRpcMsg('IS_CHANNEL_PINNED', cid, function (x) {
if (x.error || !Array.isArray(x.response)) { return void console.log(x); }
if (x.response[0] === true) {

View File

@@ -13,10 +13,10 @@ define([
var UNSORTED = module.UNSORTED = "unsorted";
var TRASH = module.TRASH = "trash";
var TEMPLATE = module.TEMPLATE = "template";
var SHARED_FOLDERS = module.SHARED_FOLDERS = "sharedFolders";
module.init = function (files, config) {
var exp = {};
var pinPads = config.pinPads;
var sframeChan = config.sframeChan;
var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Constants.storageKey;
@@ -28,23 +28,35 @@ define([
exp.UNSORTED = UNSORTED;
exp.TRASH = TRASH;
exp.TEMPLATE = TEMPLATE;
exp.SHARED_FOLDERS = SHARED_FOLDERS;
var sharedFolder = exp.sharedFolder = config.sharedFolder;
exp.id = config.id;
// Logging
var logging = function () {
console.log.apply(console, arguments);
};
var log = config.log || logging;
var log = exp.log = config.log || logging;
var logError = config.logError || logging;
var debug = exp.debug = config.debug || logging;
exp.fixFiles = function () {}; // Overriden by OuterFO
var error = exp.error = function() {
exp.fixFiles();
if (sframeChan) {
return void sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "fixFiles",
data: {}
}, function () {});
} else if (typeof (exp.fixFiles) === "function") {
exp.fixFiles();
}
console.error.apply(console, arguments);
exp.fixFiles();
};
// TODO: workgroup
var workgroup = config.workgroup;
if (pinPads) {
if (config.outer) {
// Extend "exp" with methods used only outside of the iframe (requires access to store)
OuterFO.init(config, exp, files);
}
@@ -69,7 +81,12 @@ define([
var compareFiles = function (fileA, fileB) { return fileA === fileB; };
var isSharedFolder = exp.isSharedFolder = function (element) {
if (sharedFolder) { return false; } // No recursive shared folders
return Boolean(files[SHARED_FOLDERS] && files[SHARED_FOLDERS][element]);
};
var isFile = exp.isFile = function (element, allowStr) {
if (isSharedFolder(element)) { return false; }
return typeof(element) === "number" ||
((typeof(files[OLD_FILES_DATA]) !== "undefined" || allowStr)
&& typeof(element) === "string");
@@ -78,15 +95,11 @@ define([
exp.isReadOnlyFile = function (element) {
if (!isFile(element)) { return false; }
var data = exp.getFileData(element);
var parsed = Hash.parsePadUrl(data.href);
if (!parsed) { return false; }
var pHash = parsed.hashData;
if (!pHash || pHash.type !== "pad") { return; }
return pHash && pHash.mode === 'view';
return Boolean(data.roHref && !data.href);
};
var isFolder = exp.isFolder = function (element) {
return typeof(element) === "object";
return typeof(element) === "object" || isSharedFolder(element);
};
exp.isFolderEmpty = function (element) {
if (!isFolder(element)) { return false; }
@@ -137,9 +150,11 @@ define([
// Data from filesData
var getTitle = exp.getTitle = function (file, type) {
if (workgroup) { debug("No titles in workgroups"); return; }
if (isSharedFolder(file)) {
return '??';
}
var data = getFileData(file);
if (!file || !data || !data.href) {
if (!file || !data || !(data.href || data.roHref)) {
error("getTitle called with a non-existing file id: ", file, data);
return;
}
@@ -209,14 +224,16 @@ define([
// GET FILES
var getFilesRecursively = function (root, arr) {
var getFilesRecursively = exp.getFilesRecursively = function (root, arr) {
arr = arr || [];
for (var e in root) {
if (isFile(root[e])) {
if (isFile(root[e]) || isSharedFolder(root[e])) {
if(arr.indexOf(root[e]) === -1) { arr.push(root[e]); }
} else {
getFilesRecursively(root[e], arr);
}
}
return arr;
};
var _getFiles = {};
_getFiles['array'] = function (cat) {
@@ -228,6 +245,7 @@ define([
});
_getFiles['hrefArray'] = function () {
var ret = [];
if (sharedFolder) { return ret; }
getHrefArray().forEach(function (c) {
ret = ret.concat(_getFiles[c]());
});
@@ -242,7 +260,7 @@ define([
var root = files[TRASH];
var ret = [];
var addFiles = function (el) {
if (isFile(el.element)) {
if (isFile(el.element) || isSharedFolder(el.element)) {
if(ret.indexOf(el.element) === -1) { ret.push(el.element); }
} else {
getFilesRecursively(el.element, ret);
@@ -272,10 +290,15 @@ define([
if (!files[FILES_DATA]) { return ret; }
return Object.keys(files[FILES_DATA]).map(Number);
};
_getFiles[SHARED_FOLDERS] = function () {
var ret = [];
if (!files[SHARED_FOLDERS]) { return ret; }
return Object.keys(files[SHARED_FOLDERS]).map(Number);
};
var getFiles = exp.getFiles = function (categories) {
var ret = [];
if (!categories || !categories.length) {
categories = [ROOT, 'hrefArray', TRASH, OLD_FILES_DATA, FILES_DATA];
categories = [ROOT, 'hrefArray', TRASH, OLD_FILES_DATA, FILES_DATA, SHARED_FOLDERS];
}
categories.forEach(function (c) {
if (typeof _getFiles[c] === "function") {
@@ -288,11 +311,11 @@ define([
var getIdFromHref = exp.getIdFromHref = function (href) {
var result;
getFiles([FILES_DATA]).some(function (id) {
if (files[FILES_DATA][id].href === href) {
if (files[FILES_DATA][id].href === href ||
files[FILES_DATA][id].roHref === href) {
result = id;
return true;
}
return;
});
return result;
};
@@ -308,7 +331,7 @@ define([
}
};
if (isFile(root)) {
if (isFile(root) || isSharedFolder(root)) {
if (compareFiles(file, root)) {
if (paths.indexOf(path) === -1) {
paths.push(path);
@@ -328,6 +351,7 @@ define([
return _findFileInRoot([ROOT], file);
};
var _findFileInHrefArray = function (rootName, file) {
if (sharedFolder) { return []; }
if (!files[rootName]) { return []; }
var unsorted = files[rootName].slice();
var ret = [];
@@ -338,6 +362,7 @@ define([
return ret;
};
var _findFileInTrash = function (path, file) {
if (sharedFolder) { return []; }
var root = find(path);
var paths = [];
var addPaths = function (p) {
@@ -384,11 +409,9 @@ define([
// Get drive ids of files from their channel ids
exp.findChannels = function (channels) {
var allFilesList = files[FILES_DATA];
var channels64 = channels.slice().map(Util.hexToBase64);
return getFiles([FILES_DATA]).filter(function (k) {
var data = allFilesList[k];
var parsed = Hash.parsePadUrl(data.href);
return parsed.hashData && channels64.indexOf(parsed.hashData.channel) !== -1;
return channels.indexOf(data.channel) !== -1;
});
};
@@ -556,29 +579,29 @@ define([
// DELETE
// Permanently delete multiple files at once using a list of paths
// NOTE: We have to be careful when removing elements from arrays (trash root, unsorted or template)
exp.delete = function (paths, cb, nocheck, isOwnPadRemoved) {
exp.delete = function (paths, cb, nocheck) {
if (sframeChan) {
return void sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "delete",
data: {
paths: paths,
nocheck: nocheck,
isOwnPadRemoved: isOwnPadRemoved
}
}, cb);
}
exp.deleteMultiplePermanently(paths, nocheck, isOwnPadRemoved);
if (typeof cb === "function") { cb(); }
cb = cb || function () {};
exp.deleteMultiplePermanently(paths, nocheck, cb);
//if (typeof cb === "function") { cb(); }
};
exp.emptyTrash = function (cb) {
cb = cb || function () {};
if (sframeChan) {
return void sframeChan.query("Q_DRIVE_USEROBJECT", {
cmd: "emptyTrash"
}, cb);
}
files[TRASH] = {};
exp.checkDeletedFiles();
if(cb) { cb(); }
exp.checkDeletedFiles(cb);
};
// RENAME
@@ -592,7 +615,6 @@ define([
}
}, cb);
}
console.log(path, newName);
if (path.length <= 1) {
logError('Renaming `root` is forbidden');
return;
@@ -601,7 +623,7 @@ define([
var element = find(path);
// Folders
if (isFolder(element)) {
if (isFolder(element) && !isSharedFolder(element)) {
var parentPath = path.slice();
var oldName = parentPath.pop();
if (!newName || !newName.trim() || oldName === newName) { return; }
@@ -616,8 +638,13 @@ define([
return;
}
// Files
var data = files[FILES_DATA][element];
// Files or Shared folder
var data;
if (isSharedFolder(element)) {
data = files[SHARED_FOLDERS][element];
} else {
data = files[FILES_DATA][element];
}
if (!data) { return; }
if (!newName || newName.trim() === "") {
delete data.filename;
@@ -629,6 +656,21 @@ define([
if (typeof cb === "function") { cb(); }
};
// Tags
exp.getTagsList = function () {
var tags = {};
var data;
var pushTag = function (tag) {
tags[tag] = tags[tag] ? ++tags[tag] : 1;
};
for (var id in files[FILES_DATA]) {
data = files[FILES_DATA][id];
if (!data.tags || !Array.isArray(data.tags)) { continue; }
data.tags.forEach(pushTag);
}
return tags;
};
return exp;
};
return module;