diff --git a/customize.dist/src/less2/main.less b/customize.dist/src/less2/main.less
index ca1c3503e..88f6f1f60 100644
--- a/customize.dist/src/less2/main.less
+++ b/customize.dist/src/less2/main.less
@@ -37,4 +37,5 @@ body.cp-app-todo { @import "../../../todo/app-todo.less"; }
body.cp-app-profile { @import "../../../profile/app-profile.less"; }
body.cp-app-settings { @import "../../../settings/app-settings.less"; }
body.cp-app-debug { @import "../../../debug/app-debug.less"; }
+body.cp-app-worker { @import "../../../worker/app-worker.less"; }
diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js
index 60bcf0035..31a548636 100644
--- a/www/common/cryptpad-common.js
+++ b/www/common/cryptpad-common.js
@@ -59,11 +59,6 @@ define([
// RESTRICTED
// Settings only
- common.getUserObject = function (cb) {
- postMessage("GET", [], function (obj) {
- cb(obj);
- });
- };
common.resetDrive = function (cb) {
postMessage("RESET_DRIVE", null, function (obj) {
if (obj.error) { return void cb(obj.error); }
@@ -81,6 +76,12 @@ define([
cb();
});
};
+ // Settings and drive
+ common.getUserObject = function (cb) {
+ postMessage("GET", [], function (obj) {
+ cb(obj);
+ });
+ };
// Settings and auth
common.getUserObject = function (cb) {
postMessage("GET", [], function (obj) {
@@ -94,6 +95,14 @@ define([
};
postMessage("MIGRATE_ANON_DRIVE", data, cb);
};
+ // Drive
+ common.userObjectCommand = function (data, cb) {
+ postMessage("DRIVE_USEROBJECT", data, cb);
+ };
+ common.drive = {};
+ common.drive.onLog = Util.mkEvent();
+ common.drive.onChange = Util.mkEvent();
+ common.drive.onRemove = Util.mkEvent();
// Profile
common.getProfileEditUrl = function (cb) {
postMessage("GET", ['profile', 'edit'], function (obj) {
@@ -450,6 +459,10 @@ define([
});
};
+ // Network
+ common.onNetworkDisconnect = Util.mkEvent();
+ common.onNetworkReconnect = Util.mkEvent();
+
// Messenger
var messenger = common.messenger = {};
messenger.getFriendList = function (cb) {
@@ -486,7 +499,24 @@ define([
messenger.onFriendEvent = Util.mkEvent();
messenger.onUnfriendEvent = Util.mkEvent();
- // HERE
+ // Pad RPC
+ var pad = common.padRpc = {};
+ pad.joinPad = function (data, cb) {
+ postMessage("JOIN_PAD", data, cb);
+ };
+ pad.sendPadMsg = function (data, cb) {
+ postMessage("SEND_PAD_MSG", data, cb);
+ };
+ pad.onReadyEvent = Util.mkEvent();
+ pad.onMessageEvent = Util.mkEvent();
+ pad.onJoinEvent = Util.mkEvent();
+ pad.onLeaveEvent = Util.mkEvent();
+ pad.onDisconnectEvent = Util.mkEvent();
+
+ common.getFullHistory = function (data, cb) {
+ postMessage("GET_FULL_HISTORY", data, cb);
+ };
+
common.getShareHashes = function (secret, cb) {
var hashes;
if (!window.location.hash) {
@@ -578,6 +608,13 @@ define([
common.onFriendComplete(data);
break;
}
+ // Network
+ case 'NETWORK_DISCONNECT': {
+ common.onNetworkDisconnect.fire(); break;
+ }
+ case 'NETWORK_RECONNECT': {
+ common.onNetworkReconnect.fire(data); break;
+ }
// Messenger
case 'CONTACTS_MESSAGE': {
common.messenger.onMessageEvent.fire(data); break;
@@ -597,6 +634,32 @@ define([
case 'CONTACTS_UNFRIEND': {
common.messenger.onUnfriendEvent.fire(data); break;
}
+ // Pad
+ case 'PAD_READY': {
+ common.padRpc.onReadyEvent.fire(); break;
+ }
+ case 'PAD_MESSAGE': {
+ common.padRpc.onMessageEvent.fire(data); break;
+ }
+ case 'PAD_JOIN': {
+ common.padRpc.onJoinEvent.fire(data); break;
+ }
+ case 'PAD_LEAVE': {
+ common.padRpc.onLeaveEvent.fire(data); break;
+ }
+ case 'PAD_DISCONNECT': {
+ common.padRpc.onDisconnectEvent.fire(data); break;
+ }
+ // Drive
+ case 'DRIVE_LOG': {
+ common.drive.onLog.fire(data); break;
+ }
+ case 'DRIVE_CHANGE': {
+ common.drive.onChange.fire(data); break;
+ }
+ case 'DRIVE_REMOVE': {
+ common.drive.onRemove.fire(data); break;
+ }
}
};
@@ -651,7 +714,8 @@ define([
anonHash: LocalStore.getFSHash(),
localToken: tryParsing(localStorage.getItem(Constants.tokenKey)),
language: common.getLanguage(),
- messenger: rdyCfg.messenger
+ messenger: rdyCfg.messenger,
+ driveEvents: rdyCfg.driveEvents
};
if (sessionStorage[Constants.newPadPathKey]) {
cfg.initialPath = sessionStorage[Constants.newPadPathKey];
@@ -659,6 +723,9 @@ define([
}
AStore.query("CONNECT", cfg, waitFor(function (data) {
if (data.error) { throw new Error(data.error); }
+ if (data.state === 'ALREADY_INIT') {
+ data = data.returned;
+ }
if (data.anonHash && !cfg.userHash) { LocalStore.setFSHash(data.anonHash); }
diff --git a/www/common/outer/async-store.js b/www/common/outer/async-store.js
index 7415843b1..912d74061 100644
--- a/www/common/outer/async-store.js
+++ b/www/common/outer/async-store.js
@@ -8,13 +8,14 @@ define([
'/common/common-realtime.js',
'/common/common-messaging.js',
'/common/common-messenger.js',
+ '/common/outer/chainpad-netflux-worker.js',
'/common/outer/network-config.js',
'/bower_components/chainpad-crypto/crypto.js?v=0.1.5',
'/bower_components/chainpad/chainpad.dist.js',
'/bower_components/chainpad-listmap/chainpad-listmap.js',
], function (UserObject, Migrate, Hash, Util, Constants, Feedback, Realtime, Messaging, Messenger,
- NetConfig,
+ CpNfWorker, NetConfig,
Crypto, ChainPad, Listmap) {
var Store = {};
@@ -95,7 +96,6 @@ define([
var getCanonicalChannelList = function () {
return Util.deduplicateString(getUserChannelList()).sort();
};
-
//////////////////////////////////////////////////////////////////
/////////////////////// RPC //////////////////////////////////////
//////////////////////////////////////////////////////////////////
@@ -264,7 +264,6 @@ define([
};
Store.getFileSize = function (data, cb) {
- console.log(data, cb);
if (!store.anon_rpc) { return void cb({error: 'ANON_RPC_NOT_READY'}); }
var channelId = Hash.hrefToHexChannelId(data.href);
@@ -715,11 +714,130 @@ define([
}
};
+ //////////////////////////////////////////////////////////////////
+ /////////////////////// PAD //////////////////////////////////////
+ //////////////////////////////////////////////////////////////////
+
+ // TODO with sharedworker
+ // channel will be an object storing the webchannel associated to each browser tab
+ var channel = {
+ queue: []
+ };
+ Store.joinPad = function (data, cb) {
+ var conf = {
+ onReady: function () {
+ postMessage("PAD_READY");
+ }, // post EV_PAD_READY
+ onMessage: function (m) {
+ postMessage("PAD_MESSAGE", m);
+ }, // post EV_PAD_MESSAGE
+ onJoin: function (m) {
+ postMessage("PAD_JOIN", m);
+ }, // post EV_PAD_JOIN
+ onLeave: function (m) {
+ postMessage("PAD_LEAVE", m);
+ }, // post EV_PAD_LEAVE
+ onDisconnect: function () {
+ postMessage("PAD_DISCONNECT");
+ }, // post EV_PAD_DISCONNECT
+ channel: data.channel,
+ validateKey: data.validateKey,
+ network: store.network,
+ readOnly: data.readOnly,
+ onConnect: function (wc, sendMessage) {
+ channel.sendMessage = sendMessage;
+ channel.wc = wc;
+ channel.queue.forEach(function (data) {
+ sendMessage(data.message);
+ });
+ cb({
+ myID: wc.myID,
+ id: wc.id,
+ members: wc.members
+ });
+ }
+ };
+ CpNfWorker.start(conf);
+ };
+ Store.sendPadMsg = function (data, cb) {
+ if (!channel.wc) { channel.queue.push(data); }
+ channel.sendMessage(data, cb);
+ };
+
+ // TODO
+ // GET_FULL_HISTORY from sframe-common-outer
+ Store.getFullHistory = function (data, cb) {
+ var network = store.network;
+ var hkn = network.historyKeeper;
+ //var crypto = Crypto.createEncryptor(data.keys);
+ // Get the history messages and send them to the iframe
+ var parse = function (msg) {
+ try {
+ return JSON.parse(msg);
+ } catch (e) {
+ return null;
+ }
+ };
+ var msgs = [];
+ var onMsg = function (msg) {
+ var parsed = parse(msg);
+ if (parsed[0] === 'FULL_HISTORY_END') {
+ cb(msgs);
+ return;
+ }
+ if (parsed[0] !== 'FULL_HISTORY') { return; }
+ if (parsed[1] && parsed[1].validateKey) { // First message
+ return;
+ }
+ if (parsed[1][3] !== data.channel) { return; }
+ msg = parsed[1][4];
+ if (msg) {
+ msg = msg.replace(/^cp\|/, '');
+ //var decryptedMsg = crypto.decrypt(msg, true);
+ msgs.push(msg);
+ }
+ };
+ network.on('message', onMsg);
+ network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', data.channel, data.validateKey]));
+ };
+
+ // TODO with sharedworker
+ // when the tab is closed, leave the pad
+
+ // Drive
+ Store.userObjectCommand = function (cmdData, cb) {
+ if (!cmdData || !cmdData.cmd) { return; }
+ var data = cmdData.data;
+ switch (cmdData.cmd) {
+ case 'move':
+ store.userObject.move(data.paths, data.newPath, cb); break;
+ case 'restore':
+ store.userObject.restore(data.path, cb); break;
+ case 'addFolder':
+ store.userObject.addFolder(data.path, data.name, cb); break;
+ case 'delete':
+ store.userObject.delete(data.paths, cb, data.nocheck); break;
+ case 'emptyTrash':
+ store.userObject.emptyTrash(cb); break;
+ case 'rename':
+ store.userObject.rename(data.path, data.newName, cb); break;
+ default:
+ cb();
+ }
+ };
+
+ //////////////////////////////////////////////////////////////////
+ /////////////////////// Init /////////////////////////////////////
+ //////////////////////////////////////////////////////////////////
+
var onReady = function (returned, cb) {
var proxy = store.proxy;
var userObject = store.userObject = UserObject.init(proxy.drive, {
pinPads: Store.pinPads,
- loggedIn: store.loggedIn
+ loggedIn: store.loggedIn,
+ log: function (msg) {
+ postMessage("DRIVE_LOG", msg);
+ }
});
var todo = function () {
userObject.fixFiles();
@@ -811,7 +929,7 @@ define([
ChainPad: ChainPad,
classic: true,
};
- var rt = Listmap.create(listmapConfig);
+ var rt = window.rt = Listmap.create(listmapConfig);
store.proxy = rt.proxy;
store.loggedIn = typeof(data.userHash) !== "undefined";
@@ -840,8 +958,16 @@ define([
if (path[0] === 'drive' && path[1] === "migrate" && value === 1) {
rt.network.disconnect();
rt.realtime.abort();
+ postMessage('NETWORK_DISCONNECT');
}
});
+
+ rt.proxy.on('disconnect', function () {
+ postMessage('NETWORK_DISCONNECT');
+ });
+ rt.proxy.on('reconnect', function (info) {
+ postMessage('NETWORK_RECONNECT', {myId: info.myId});
+ });
};
/**
@@ -858,7 +984,8 @@ define([
Store.init = function (data, callback) {
if (initialized) {
return void callback({
- error: 'ALREADY_INIT'
+ state: 'ALREADY_INIT',
+ returned: store.returned
});
}
initialized = true;
@@ -873,14 +1000,32 @@ define([
if (Object.keys(store.proxy).length === 1) {
Feedback.send("FIRST_APP_USE", true);
}
+ store.returned = ret;
callback(ret);
var messagingCfg = getMessagingCfg();
Messaging.addDirectMessageHandler(messagingCfg);
+ // Send events whenever there is a change or a removal in the drive
+ if (data.driveEvents) {
+ store.proxy.on('change', [], function (o, n, p) {
+ postMessage('DRIVE_CHANGE', {
+ old: o,
+ new: n,
+ path: p
+ });
+ });
+ store.proxy.on('remove', [], function (o, p) {
+ postMessage('DRIVE_REMOVE', {
+ old: o,
+ path: p
+ });
+ });
+ }
+
if (data.messenger) {
- var messenger = store.messenger = Messenger.messenger(store); // TODO
+ var messenger = store.messenger = Messenger.messenger(store);
messenger.on('message', function (message) {
postMessage('CONTACTS_MESSAGE', message);
});
diff --git a/www/common/outer/chainpad-netflux-worker.js b/www/common/outer/chainpad-netflux-worker.js
new file mode 100644
index 000000000..7ca8a4104
--- /dev/null
+++ b/www/common/outer/chainpad-netflux-worker.js
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2014 XWiki SAS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+define([], function () {
+ var USE_HISTORY = true;
+
+ var verbose = function (x) { console.log(x); };
+ verbose = function () {}; // comment out to enable verbose logging
+
+ var unBencode = function (str) { return str.replace(/^\d+:/, ''); };
+
+ var start = function (conf) {
+ var channel = conf.channel;
+ var validateKey = conf.validateKey;
+ var readOnly = conf.readOnly || false;
+ var network = conf.network;
+ var onConnect = conf.onConnect || function () { };
+ var onMessage = conf.onMessage;
+ var onJoin = conf.onJoin;
+ var onLeave = conf.onLeave;
+ var onReady = conf.onReady;
+ var onDisconnect = conf.onDisconnect;
+ conf = undefined;
+
+ var initializing = true;
+ var lastKnownHash;
+
+ var messageFromOuter = function () {};
+
+ var onRdy = function () {
+ // Trigger onReady only if not ready yet. This is important because the history keeper sends a direct
+ // message through "network" when it is synced, and it triggers onReady for each channel joined.
+ if (!initializing) { return; }
+ onReady();
+ //sframeChan.event('EV_RT_READY', null);
+ // we're fully synced
+ initializing = false;
+ };
+
+ // shim between chainpad and netflux
+ var msgIn = function (peerId, msg) {
+ return msg.replace(/^cp\|/, '');
+
+ /*try {
+ var decryptedMsg = Crypto.decrypt(msg, validateKey);
+ return decryptedMsg;
+ } catch (err) {
+ console.error(err);
+ return msg;
+ }*/
+ };
+
+ var msgOut = function (msg) {
+ if (readOnly) { return; }
+ return msg;
+ /*try {
+ var cmsg = Crypto.encrypt(msg);
+ if (msg.indexOf('[4') === 0) { cmsg = 'cp|' + cmsg; }
+ return cmsg;
+ } catch (err) {
+ console.log(msg);
+ throw err;
+ }*/
+ };
+
+ var onMsg = function(peer, msg, wc, network, direct) {
+ // unpack the history keeper from the webchannel
+ var hk = network.historyKeeper;
+
+ if (direct && peer !== hk) {
+ return;
+ }
+ if (direct) {
+ var parsed = JSON.parse(msg);
+ if (parsed.validateKey && parsed.channel) {
+ if (parsed.channel === wc.id && !validateKey) {
+ validateKey = parsed.validateKey;
+ }
+ // We have to return even if it is not the current channel:
+ // we don't want to continue with other channels messages here
+ return;
+ }
+ if (parsed.state && parsed.state === 1 && parsed.channel) {
+ if (parsed.channel === wc.id) {
+ onRdy();
+ }
+ // We have to return even if it is not the current channel:
+ // we don't want to continue with other channels messages here
+ return;
+ }
+ }
+ if (peer === hk) {
+ // if the peer is the 'history keeper', extract their message
+ var parsed1 = JSON.parse(msg);
+ msg = parsed1[4];
+ // Check that this is a message for our channel
+ if (parsed1[3] !== wc.id) { return; }
+ }
+
+ lastKnownHash = msg.slice(0,64);
+ var message = msgIn(peer, msg);
+
+ verbose(message);
+
+ // slice off the bencoded header
+ // Why are we getting bencoded stuff to begin with?
+ // FIXME this shouldn't be necessary
+ message = unBencode(message);//.slice(message.indexOf(':[') + 1);
+
+ // pass the message into Chainpad
+ onMessage(message);
+ //sframeChan.query('Q_RT_MESSAGE', message, function () { });
+ };
+
+ // We use an object to store the webchannel so that we don't have to push new handlers to chainpad
+ // and remove the old ones when reconnecting and keeping the same 'realtime' object
+ // See realtime.onMessage below: we call wc.bcast(...) but wc may change
+ var wcObject = {};
+ var onOpen = function(wc, network, firstConnection) {
+ wcObject.wc = wc;
+ channel = wc.id;
+
+ // Add the existing peers in the userList
+ //TODO sframeChan.event('EV_RT_CONNECT', { myID: wc.myID, members: wc.members, readOnly: readOnly });
+
+ // Add the handlers to the WebChannel
+ wc.on('message', function (msg, sender) { //Channel msg
+ onMsg(sender, msg, wc, network);
+ });
+ wc.on('join', function (m) { onJoin(m); /*sframeChan.event('EV_RT_JOIN', m);*/ });
+ wc.on('leave', function (m) { onLeave(m); /*sframeChan.event('EV_RT_LEAVE', m);*/ });
+
+ if (firstConnection) {
+ // Sending a message...
+ messageFromOuter = function(message, cb) {
+ // Filter messages sent by Chainpad to make it compatible with Netflux
+ message = msgOut(message);
+ if (message) {
+ // Do not remove wcObject, it allows us to use a new 'wc' without changing the handler if we
+ // want to keep the same chainpad (realtime) object
+ try {
+ wcObject.wc.bcast(message).then(function() {
+ cb();
+ }, function(err) {
+ // The message has not been sent, display the error.
+ console.error(err);
+ });
+ } catch (e) {
+ console.log(e);
+ // Just skip calling back and it will fail on the inside.
+ }
+ }
+ };
+ }
+
+ onConnect(wc, messageFromOuter);
+
+ // Get the channel history
+ if (USE_HISTORY) {
+ var hk;
+
+ wc.members.forEach(function (p) {
+ if (p.length === 16) { hk = p; }
+ });
+ network.historyKeeper = hk;
+
+ var msg = ['GET_HISTORY', wc.id];
+ // Add the validateKey if we are the channel creator and we have a validateKey
+ msg.push(validateKey);
+ msg.push(lastKnownHash);
+ if (hk) { network.sendto(hk, JSON.stringify(msg)); }
+ } else {
+ onRdy();
+ }
+ };
+
+ /*var isIntentionallyLeaving = false;
+ window.addEventListener("beforeunload", function () {
+ isIntentionallyLeaving = true;
+ });*/
+
+ var findChannelById = function (webChannels, channelId) {
+ var webChannel;
+
+ // Array.some terminates once a truthy value is returned
+ // best case is faster than forEach, though webchannel arrays seem
+ // to consistently have a length of 1
+ webChannels.some(function(chan) {
+ if(chan.id === channelId) { webChannel = chan; return true;}
+ });
+ return webChannel;
+ };
+
+ var connectTo = function (network, firstConnection) {
+ // join the netflux network, promise to handle opening of the channel
+ network.join(channel || null).then(function(wc) {
+ onOpen(wc, network, firstConnection);
+ }, function(error) {
+ console.error(error);
+ });
+ };
+
+ network.on('disconnect', function (reason) {
+ console.log('disconnect');
+ //if (isIntentionallyLeaving) { return; }
+ if (reason === "network.disconnect() called") { return; }
+ onDisconnect();
+ //sframeChan.event('EV_RT_DISCONNECT');
+ });
+
+ network.on('reconnect', function () {
+ initializing = true;
+ connectTo(network, false);
+ });
+
+ network.on('message', function (msg, sender) { // Direct message
+ var wchan = findChannelById(network.webChannels, channel);
+ if (wchan) {
+ onMsg(sender, msg, wchan, network, true);
+ }
+ });
+
+ connectTo(network, true);
+ };
+
+ return {
+ start: start
+ /*function (config) {
+ config.sframeChan.whenReg('EV_RT_READY', function () {
+ start(config);
+ });
+ }*/
+ };
+});
+
diff --git a/www/common/outer/store-rpc.js b/www/common/outer/store-rpc.js
index c0b435079..cabaeaea2 100644
--- a/www/common/outer/store-rpc.js
+++ b/www/common/outer/store-rpc.js
@@ -146,6 +146,20 @@ define([
case 'CONTACTS_SET_CHANNEL_HEAD': {
Store.messenger.setChannelHead(data, cb); break;
}
+ // 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;
+ }
+ // Drive
+ case 'DRIVE_USEROBJECT': {
+ Store.userObjectCommand(data, cb); break;
+ }
default: {
break;
diff --git a/www/common/outer/store-worker.js b/www/common/outer/store-worker.js
new file mode 100644
index 000000000..a4b4d159d
--- /dev/null
+++ b/www/common/outer/store-worker.js
@@ -0,0 +1,33 @@
+/* jshint ignore:start */
+
+var window = self;
+importScripts('/bower_components/requirejs/require.js');
+
+require.config({
+ // fix up locations so that relative urls work.
+ baseUrl: '/',
+ paths: {
+ // jquery declares itself as literally "jquery" so it cannot be pulled by path :(
+ "jquery": "/bower_components/jquery/dist/jquery.min",
+ // json.sortify same
+ "json.sortify": "/bower_components/json.sortify/dist/JSON.sortify",
+ //"pdfjs-dist/build/pdf": "/bower_components/pdfjs-dist/build/pdf",
+ //"pdfjs-dist/build/pdf.worker": "/bower_components/pdfjs-dist/build/pdf.worker"
+ cm: '/bower_components/codemirror'
+ },
+ map: {
+ '*': {
+ 'css': '/bower_components/require-css/css.js',
+ 'less': '/common/RequireLess.js',
+ }
+ }
+});
+
+onconnect = function(e) {
+ var port = e.ports[0];
+ port.postMessage({state: 'READY'});
+ port.onmessage = function (e) {
+ };
+};
+
+
diff --git a/www/common/outer/userObject.js b/www/common/outer/userObject.js
new file mode 100644
index 000000000..10a796e3c
--- /dev/null
+++ b/www/common/outer/userObject.js
@@ -0,0 +1,582 @@
+define([
+ '/customize/application_config.js',
+ '/common/common-util.js',
+ '/common/common-hash.js',
+ '/common/common-realtime.js',
+ '/customize/messages.js'
+], function (AppConfig, Util, Hash, Realtime, Messages) {
+ var module = {};
+
+ var clone = function (o) {
+ try { return JSON.parse(JSON.stringify(o)); }
+ catch (e) { return undefined; }
+ };
+
+ module.init = function (config, exp, files) {
+ var pinPads = config.pinPads;
+ var loggedIn = config.loggedIn;
+ var workgroup = config.workgroup;
+
+ var ROOT = exp.ROOT;
+ var FILES_DATA = exp.FILES_DATA;
+ var OLD_FILES_DATA = exp.OLD_FILES_DATA;
+ var UNSORTED = exp.UNSORTED;
+ var TRASH = exp.TRASH;
+ var TEMPLATE = exp.TEMPLATE;
+
+ var debug = exp.debug;
+
+ exp.setPadAttribute = function (href, attr, value, cb) {
+ cb = cb || function () {};
+ var id = exp.getIdFromHref(href);
+ if (!id) { return void cb("E_INVAL_HREF"); }
+ if (!attr || !attr.trim()) { return void cb("E_INVAL_ATTR"); }
+ var data = exp.getFileData(id);
+ data[attr] = clone(value);
+ cb(null);
+ };
+ exp.getPadAttribute = function (href, attr, cb) {
+ cb = cb || function () {};
+ var id = exp.getIdFromHref(href);
+ if (!id) { return void cb(null, undefined); }
+ 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();
+ }
+ if (!pinPads) { return; }
+ pinPads([Hash.hrefToHexChannelId(data.href)], function (obj) {
+ if (obj && obj.error) { return void cb(obj.error); }
+ todo();
+ });
+ };
+
+ // FILES DATA
+ var spliceFileData = function (id) {
+ delete files[FILES_DATA][id];
+ };
+
+ exp.checkDeletedFiles = function () {
+ // Nothing in OLD_FILES_DATA for workgroups
+ if (workgroup || (!loggedIn && !config.testMode)) { return; }
+
+ var filesList = exp.getFiles([ROOT, 'hrefArray', TRASH]);
+ exp.getFiles([FILES_DATA]).forEach(function (id) {
+ if (filesList.indexOf(id) === -1) {
+ spliceFileData(id);
+ }
+ });
+ };
+ var deleteHrefs = function (ids) {
+ ids.forEach(function (obj) {
+ var idx = files[obj.root].indexOf(obj.id);
+ files[obj.root].splice(idx, 1);
+ });
+ };
+ var deleteMultipleTrashRoot = function (roots) {
+ roots.forEach(function (obj) {
+ var idx = files[TRASH][obj.name].indexOf(obj.el);
+ files[TRASH][obj.name].splice(idx, 1);
+ });
+ };
+ exp.deleteMultiplePermanently = function (paths, nocheck) {
+ 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]); });
+ var allFilesPaths = paths.filter(function(x) { return exp.isPathIn(x, [FILES_DATA]); });
+
+ if (!loggedIn && !config.testMode) {
+ allFilesPaths.forEach(function (path) {
+ var el = exp.find(path);
+ if (!el) { return; }
+ var id = exp.getIdFromHref(el.href);
+ if (!id) { return; }
+ spliceFileData(id);
+ removePadAttribute(el.href);
+ });
+ return;
+ }
+
+ var ids = [];
+ hrefPaths.forEach(function (path) {
+ var id = exp.find(path);
+ ids.push({
+ root: path[0],
+ id: id
+ });
+ });
+ deleteHrefs(ids);
+
+ rootPaths.forEach(function (path) {
+ var parentPath = path.slice();
+ var key = parentPath.pop();
+ var parentEl = exp.find(parentPath);
+ delete parentEl[key];
+ });
+
+ var trashRoot = [];
+ trashPaths.forEach(function (path) {
+ var parentPath = path.slice();
+ var key = parentPath.pop();
+ var parentEl = exp.find(parentPath);
+ // Trash root: we have array here, we can't just splice with the path otherwise we might break the path
+ // of another element in the loop
+ if (path.length === 4) {
+ trashRoot.push({
+ name: path[1],
+ el: parentEl
+ });
+ return;
+ }
+ // Trash but not root: it's just a tree so remove the key
+ delete parentEl[key];
+ });
+ 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(); }
+ };
+
+ // Move
+ var pushToTrash = function (name, element, path) {
+ var trash = files[TRASH];
+ if (typeof(trash[name]) === "undefined") { trash[name] = []; }
+ var trashArray = trash[name];
+ var trashElement = {
+ element: element,
+ path: path
+ };
+ trashArray.push(trashElement);
+ };
+ exp.copyElement = function (elementPath, newParentPath) {
+ if (exp.comparePath(elementPath, newParentPath)) { return; } // Nothing to do...
+ var element = exp.find(elementPath);
+ var newParent = exp.find(newParentPath);
+
+ // Move to Trash
+ if (exp.isPathIn(newParentPath, [TRASH])) {
+ if (!elementPath || elementPath.length < 2 || elementPath[0] === TRASH) {
+ debug("Can't move an element from the trash to the trash: ", elementPath);
+ return;
+ }
+ var key = elementPath[elementPath.length - 1];
+ var elName = exp.isPathIn(elementPath, ['hrefArray']) ? exp.getTitle(element) : key;
+ var parentPath = elementPath.slice();
+ parentPath.pop();
+ pushToTrash(elName, element, parentPath);
+ return true;
+ }
+ // Move to hrefArray
+ if (exp.isPathIn(newParentPath, ['hrefArray'])) {
+ if (exp.isFolder(element)) {
+ exp.log(Messages.fo_moveUnsortedError);
+ return;
+ } else {
+ if (elementPath[0] === newParentPath[0]) { return; }
+ var fileRoot = newParentPath[0];
+ if (files[fileRoot].indexOf(element) === -1) {
+ files[fileRoot].push(element);
+ }
+ return true;
+ }
+ }
+ // Move to root
+ var newName = exp.isFile(element) ?
+ exp.getAvailableName(newParent, Hash.createChannelId()) :
+ exp.isInTrashRoot(elementPath) ?
+ elementPath[1] : elementPath.pop();
+
+ if (typeof(newParent[newName]) !== "undefined") {
+ exp.log(Messages.fo_unavailableName);
+ return;
+ }
+ newParent[newName] = element;
+ return true;
+ };
+
+ // FORGET (move with href not path)
+ exp.forget = function (href) {
+ var id = exp.getIdFromHref(href);
+ if (!id) { return; }
+ if (!loggedIn && !config.testMode) {
+ // delete permanently
+ exp.removePadAttribute(href);
+ spliceFileData(id);
+ return;
+ }
+ var paths = exp.findFile(id);
+ exp.move(paths, [TRASH]);
+ };
+
+ // 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.
+ // This is use with setPadTitle when we open a stronger version of a deleted pad
+ exp.restoreHref = function (href) {
+ var idO = exp.getIdFromHref(href);
+
+ if (!idO || !exp.isFile(idO)) { return; }
+
+ var paths = exp.findFile(idO);
+
+ // Remove all the occurences in the trash
+ // If all the occurences are in the trash or no occurence, add the pad to root
+ var allInTrash = true;
+ paths.forEach(function (p) {
+ if (p[0] === TRASH) {
+ exp.delete(p, null, true); // 3rd parameter means skip "checkDeletedFiles"
+ return;
+ }
+ allInTrash = false;
+ });
+ if (allInTrash) {
+ exp.add(idO);
+ }
+ };
+
+ exp.add = function (id, path) {
+ // TODO WW
+ if (!loggedIn && !config.testMode) { return; }
+ var data = files[FILES_DATA][id];
+ if (!data || typeof(data) !== "object") { return; }
+ var newPath = path, parentEl;
+ if (path && !Array.isArray(path)) {
+ newPath = decodeURIComponent(path).split(',');
+ }
+ // Add to href array
+ if (path && exp.isPathIn(newPath, ['hrefArray'])) {
+ parentEl = exp.find(newPath);
+ parentEl.push(id);
+ return;
+ }
+ // Add to root if path is ROOT or if no path
+ var filesList = exp.getFiles([ROOT, TRASH, 'hrefArray']);
+ if (path && exp.isPathIn(newPath, [ROOT]) || filesList.indexOf(id) === -1) {
+ parentEl = exp.find(newPath || [ROOT]);
+ if (parentEl) {
+ var newName = exp.getAvailableName(parentEl, Hash.createChannelId());
+ parentEl[newName] = id;
+ return;
+ }
+ }
+ };
+
+ /**
+ * INTEGRITY CHECK
+ */
+
+ exp.migrate = function (cb) {
+ // Make sure unsorted doesn't exist anymore
+ // Note: Unsorted only works with the old structure where pads are href
+ // It should be called before the migration code
+ var fixUnsorted = function () {
+ if (!files[UNSORTED] || !files[OLD_FILES_DATA]) { return; }
+ debug("UNSORTED still exists in the object, removing it...");
+ var us = files[UNSORTED];
+ if (us.length === 0) {
+ delete files[UNSORTED];
+ return;
+ }
+ us.forEach(function (el) {
+ if (typeof el !== "string") {
+ return;
+ }
+ var data = files[OLD_FILES_DATA].filter(function (x) {
+ return x.href === el;
+ });
+ if (data.length === 0) {
+ files[OLD_FILES_DATA].push({
+ href: el
+ });
+ }
+ return;
+ });
+ delete files[UNSORTED];
+ };
+ // mergeDrive...
+ var migrateToNewFormat = function (todo) {
+ if (!files[OLD_FILES_DATA]) {
+ return void todo();
+ }
+ try {
+ debug("Migrating file system...");
+ files.migrate = 1;
+ var next = function () {
+ var oldData = files[OLD_FILES_DATA].slice();
+ if (!files[FILES_DATA]) {
+ files[FILES_DATA] = {};
+ }
+ var newData = files[FILES_DATA];
+ //var oldFiles = oldData.map(function (o) { return o.href; });
+ oldData.forEach(function (obj) {
+ if (!obj || !obj.href) { return; }
+ var href = obj.href;
+ var id = Util.createRandomInteger();
+ var paths = exp.findFile(href);
+ var data = obj;
+ var key = Hash.createChannelId();
+ if (data) {
+ newData[id] = data;
+ } else {
+ newData[id] = {href: href};
+ }
+ paths.forEach(function (p) {
+ var parentPath = p.slice();
+ var okey = parentPath.pop(); // get the parent
+ var parent = exp.find(parentPath);
+ if (exp.isInTrashRoot(p)) {
+ parent.element = id;
+ newData[id].filename = p[1];
+ return;
+ }
+ if (exp.isPathIn(p, ['hrefArray'])) {
+ parent[okey] = id;
+ return;
+ }
+ // else root or trash (not trashroot)
+ parent[key] = id;
+ newData[id].filename = okey;
+ delete parent[okey];
+ });
+ });
+ delete files[OLD_FILES_DATA];
+ delete files.migrate;
+ console.log('done');
+ todo();
+ };
+ if (exp.rt) {
+ exp.rt.sync();
+ // TODO
+ Realtime.whenRealtimeSyncs(exp.rt, next);
+ } else {
+ window.setTimeout(next, 1000);
+ }
+ } catch(e) {
+ console.error(e);
+ todo();
+ }
+ };
+
+ fixUnsorted();
+ migrateToNewFormat(cb);
+ };
+
+ exp.fixFiles = function () {
+ // 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
+ // * TRASH: Trash root contains only arrays, each element of the array is an object {element:.., path:..}
+ // * OLD_FILES_DATA: - Data (title, cdate, adte) are stored in filesData. filesData contains only href keys linking to object with title, cdate, adate.
+ // - 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
+ debug("Cleaning file system...");
+
+ var before = JSON.stringify(files);
+
+ var fixRoot = function (elem) {
+ if (typeof(files[ROOT]) !== "object") { debug("ROOT was not an object"); files[ROOT] = {}; }
+ var element = elem || files[ROOT];
+ for (var el in element) {
+ if (!exp.isFile(element[el], true) && !exp.isFolder(element[el])) {
+ debug("An element in ROOT was not a folder nor a file. ", element[el]);
+ delete element[el];
+ continue;
+ }
+ if (exp.isFolder(element[el])) {
+ fixRoot(element[el]);
+ continue;
+ }
+ if (typeof element[el] === "string") {
+ // We have an old file (href) which is not in filesData: add it
+ var id = Util.createRandomInteger();
+ var key = Hash.createChannelId();
+ files[FILES_DATA][id] = {href: element[el], filename: el};
+ element[key] = id;
+ delete element[el];
+ }
+ if (typeof element[el] === "number") {
+ var data = files[FILES_DATA][element[el]];
+ if (!data) {
+ debug("An element in ROOT doesn't have associated data", element[el], el);
+ delete element[el];
+ }
+ }
+ }
+ };
+ var fixTrashRoot = function () {
+ if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; }
+ var tr = files[TRASH];
+ var toClean;
+ var addToClean = function (obj, idx, el) {
+ if (typeof(obj) !== "object") { toClean.push(idx); return; }
+ if (!exp.isFile(obj.element, true) &&
+ !exp.isFolder(obj.element)) { toClean.push(idx); return; }
+ if (!Array.isArray(obj.path)) { toClean.push(idx); return; }
+ if (typeof obj.element === "string") {
+ // We have an old file (href) which is not in filesData: add it
+ var id = Util.createRandomInteger();
+ files[FILES_DATA][id] = {href: obj.element, filename: el};
+ obj.element = id;
+ }
+ if (exp.isFolder(obj.element)) { fixRoot(obj.element); }
+ if (typeof obj.element === "number") {
+ var data = files[FILES_DATA][obj.element];
+ if (!data) {
+ debug("An element in TRASH doesn't have associated data", obj.element, el);
+ toClean.push(idx);
+ }
+ }
+
+ };
+ for (var el in tr) {
+ if (!Array.isArray(tr[el])) {
+ debug("An element in TRASH root is not an array. ", tr[el]);
+ delete tr[el];
+ } else if (tr[el].length === 0) {
+ debug("Empty array in TRASH root. ", tr[el]);
+ delete tr[el];
+ } else {
+ toClean = [];
+ for (var j=0; j
=0; i--) {
+ tr[el].splice(toClean[i], 1);
+ }
+ }
+ }
+ };
+ var fixTemplate = function () {
+ if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; }
+ files[TEMPLATE] = Util.deduplicateString(files[TEMPLATE].slice());
+ var us = files[TEMPLATE];
+ var rootFiles = exp.getFiles([ROOT]).slice();
+ var toClean = [];
+ us.forEach(function (el, idx) {
+ if (!exp.isFile(el, true) || rootFiles.indexOf(el) !== -1) {
+ toClean.push(el);
+ }
+ if (typeof el === "string") {
+ // We have an old file (href) which is not in filesData: add it
+ var id = Util.createRandomInteger();
+ files[FILES_DATA][id] = {href: el};
+ us[idx] = id;
+ }
+ if (typeof el === "number") {
+ var data = files[FILES_DATA][el];
+ if (!data) {
+ debug("An element in TEMPLATE doesn't have associated data", el);
+ toClean.push(el);
+ }
+ }
+ });
+ toClean.forEach(function (el) {
+ var idx = us.indexOf(el);
+ if (idx !== -1) {
+ us.splice(idx, 1);
+ }
+ });
+ };
+ var fixFilesData = function () {
+ if (typeof files[FILES_DATA] !== "object") { debug("OLD_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]);
+ var toClean = [];
+ for (var id in fd) {
+ id = Number(id);
+ var el = fd[id];
+ if (!el || typeof(el) !== "object") {
+ debug("An element in filesData was not an object.", el);
+ toClean.push(id);
+ continue;
+ }
+ if (!el.href) {
+ 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); }
+ if (!parsed.hash) {
+ debug("Removing an element in filesData with a invalid href.", el);
+ toClean.push(id);
+ continue;
+ }
+ if (!parsed.type) {
+ debug("Removing an element in filesData with a invalid type.", el);
+ toClean.push(id);
+ continue;
+ }
+
+ 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();
+ root[newName] = id;
+ continue;
+ }
+ }
+ toClean.forEach(function (id) {
+ spliceFileData(id);
+ });
+ };
+
+ var fixDrive = function () {
+ Object.keys(files).forEach(function (key) {
+ if (key.slice(0,1) === '/') { delete files[key]; }
+ });
+ };
+
+ fixRoot();
+ fixTrashRoot();
+ if (!workgroup) {
+ fixTemplate();
+ fixFilesData();
+ }
+ fixDrive();
+
+ 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");
+ return;
+ }
+ debug("File system was clean");
+ };
+
+ return exp;
+ };
+
+ return module;
+});
diff --git a/www/common/sframe-chainpad-netflux-outer.js b/www/common/sframe-chainpad-netflux-outer.js
index b62a25a05..7d991a273 100644
--- a/www/common/sframe-chainpad-netflux-outer.js
+++ b/www/common/sframe-chainpad-netflux-outer.js
@@ -15,8 +15,6 @@
* along with this program. If not, see .
*/
define([], function () {
- var USE_HISTORY = true;
-
var verbose = function (x) { console.log(x); };
verbose = function () {}; // comment out to enable verbose logging
@@ -27,32 +25,17 @@ define([], function () {
var Crypto = conf.crypto;
var validateKey = conf.validateKey;
var readOnly = conf.readOnly || false;
- var network = conf.network;
+ var padRpc = conf.padRpc;
var sframeChan = conf.sframeChan;
var onConnect = conf.onConnect || function () { };
conf = undefined;
- var initializing = true;
- var lastKnownHash;
-
- var queue = [];
- var messageFromInner = function (m, cb) { queue.push([ m, cb ]); };
- sframeChan.on('Q_RT_MESSAGE', function (message, cb) {
- messageFromInner(message, cb);
- });
-
- var onReady = function () {
- // Trigger onReady only if not ready yet. This is important because the history keeper sends a direct
- // message through "network" when it is synced, and it triggers onReady for each channel joined.
- if (!initializing) { return; }
+ padRpc.onReadyEvent.reg(function () {
sframeChan.event('EV_RT_READY', null);
- // we're fully synced
- initializing = false;
- };
+ });
// shim between chainpad and netflux
- var msgIn = function (peerId, msg) {
- msg = msg.replace(/^cp\|/, '');
+ var msgIn = function (msg) {
try {
var decryptedMsg = Crypto.decrypt(msg, validateKey);
return decryptedMsg;
@@ -74,44 +57,14 @@ define([], function () {
}
};
- var onMessage = function(peer, msg, wc, network, direct) {
- // unpack the history keeper from the webchannel
- var hk = network.historyKeeper;
-
- if (direct && peer !== hk) {
- return;
- }
- if (direct) {
- var parsed = JSON.parse(msg);
- if (parsed.validateKey && parsed.channel) {
- if (parsed.channel === wc.id && !validateKey) {
- validateKey = parsed.validateKey;
- }
- // We have to return even if it is not the current channel:
- // we don't want to continue with other channels messages here
- return;
- }
- if (parsed.state && parsed.state === 1 && parsed.channel) {
- if (parsed.channel === wc.id) {
- onReady(wc);
- }
- // We have to return even if it is not the current channel:
- // we don't want to continue with other channels messages here
- return;
- }
- }
- // The history keeper is different for each channel :
- // no need to check if the message is related to the current channel
- if (peer === hk) {
- // if the peer is the 'history keeper', extract their message
- var parsed1 = JSON.parse(msg);
- msg = parsed1[4];
- // Check that this is a message for us
- if (parsed1[3] !== wc.id) { return; }
- }
+ sframeChan.on('Q_RT_MESSAGE', function (message, cb) {
+ var msg = msgOut(message);
+ if (!msg) { return; }
+ padRpc.sendPadMsg(msg, cb);
+ });
- lastKnownHash = msg.slice(0,64);
- var message = msgIn(peer, msg);
+ var onMessage = function(msg) {
+ var message = msgIn(msg);
verbose(message);
@@ -124,118 +77,31 @@ define([], function () {
sframeChan.query('Q_RT_MESSAGE', message, function () { });
};
- // We use an object to store the webchannel so that we don't have to push new handlers to chainpad
- // and remove the old ones when reconnecting and keeping the same 'realtime' object
- // See realtime.onMessage below: we call wc.bcast(...) but wc may change
- var wcObject = {};
- var onOpen = function(wc, network, firstConnection) {
- wcObject.wc = wc;
- channel = wc.id;
-
- onConnect(wc);
- onConnect = function () { };
-
+ var onOpen = function(data) {
// Add the existing peers in the userList
- sframeChan.event('EV_RT_CONNECT', { myID: wc.myID, members: wc.members, readOnly: readOnly });
-
- // Add the handlers to the WebChannel
- wc.on('message', function (msg, sender) { //Channel msg
- onMessage(sender, msg, wc, network);
- });
- wc.on('join', function (m) { sframeChan.event('EV_RT_JOIN', m); });
- wc.on('leave', function (m) { sframeChan.event('EV_RT_LEAVE', m); });
-
- if (firstConnection) {
- // Sending a message...
- messageFromInner = function(message, cb) {
- // Filter messages sent by Chainpad to make it compatible with Netflux
- message = msgOut(message);
- if (message) {
- // Do not remove wcObject, it allows us to use a new 'wc' without changing the handler if we
- // want to keep the same chainpad (realtime) object
- try {
- if (window.Cryptpad_SUPPRESS_MSG) { return; }
- wcObject.wc.bcast(message).then(function() {
- if (window.Cryptpad_SUPPRESS_ACK) { return; }
- cb();
- }, function(err) {
- // The message has not been sent, display the error.
- console.error(err);
- });
- } catch (e) {
- console.log(e);
- // Just skip calling back and it will fail on the inside.
- }
- }
- };
- queue.forEach(function (arr) { messageFromInner(arr[0], arr[1]); });
- }
-
- // Get the channel history
- if (USE_HISTORY) {
- var hk;
-
- wc.members.forEach(function (p) {
- if (p.length === 16) { hk = p; }
- });
- network.historyKeeper = hk;
-
- var msg = ['GET_HISTORY', wc.id];
- // Add the validateKey if we are the channel creator and we have a validateKey
- msg.push(validateKey);
- msg.push(lastKnownHash);
- if (hk) { network.sendto(hk, JSON.stringify(msg)); }
- } else {
- onReady(wc);
- }
- };
-
- var isIntentionallyLeaving = false;
- window.addEventListener("beforeunload", function () {
- isIntentionallyLeaving = true;
- });
-
- var findChannelById = function (webChannels, channelId) {
- var webChannel;
+ onConnect(data.id);
+ onConnect = function () {};
- // Array.some terminates once a truthy value is returned
- // best case is faster than forEach, though webchannel arrays seem
- // to consistently have a length of 1
- webChannels.some(function(chan) {
- if(chan.id === channelId) { webChannel = chan; return true;}
- });
- return webChannel;
- };
+ sframeChan.event('EV_RT_CONNECT', { myID: data.myID, members: data.members, readOnly: readOnly });
- var connectTo = function (network, firstConnection) {
- // join the netflux network, promise to handle opening of the channel
- network.join(channel || null).then(function(wc) {
- onOpen(wc, network, firstConnection);
- }, function(error) {
- console.error(error);
- });
+ // Add the handlers to the WebChannel
+ padRpc.onMessageEvent.reg(function (msg) { onMessage(msg); });
+ padRpc.onJoinEvent.reg(function (m) { sframeChan.event('EV_RT_JOIN', m); });
+ padRpc.onLeaveEvent.reg(function (m) { sframeChan.event('EV_RT_LEAVE', m); });
};
- network.on('disconnect', function (reason) {
- console.log('disconnect');
- if (isIntentionallyLeaving) { return; }
- if (reason === "network.disconnect() called") { return; }
+ padRpc.onDisconnectEvent.reg(function () {
sframeChan.event('EV_RT_DISCONNECT');
});
- network.on('reconnect', function () {
- initializing = true;
- connectTo(network, false);
+ // join the netflux network, promise to handle opening of the channel
+ padRpc.joinPad({
+ channel: channel || null,
+ validateKey: validateKey,
+ readOnly: readOnly
+ }, function(data) {
+ onOpen(data);
});
-
- network.on('message', function (msg, sender) { // Direct message
- var wchan = findChannelById(network.webChannels, channel);
- if (wchan) {
- onMessage(sender, msg, wchan, network, true);
- }
- });
-
- connectTo(network, true);
};
return {
diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js
index c9c6c9526..19e4f315c 100644
--- a/www/common/sframe-common-outer.js
+++ b/www/common/sframe-common-outer.js
@@ -86,7 +86,8 @@ define([
sframeChan = sfc;
}), false, { cache: cache, localStore: localStore, language: Cryptpad.getLanguage() });
Cryptpad.ready(waitFor(), {
- messenger: cfg.messaging
+ messenger: cfg.messaging,
+ driveEvents: cfg.driveEvents
});
if (!cfg.newNetwork) {
@@ -298,36 +299,15 @@ define([
};
sframeChan.on('Q_GET_FULL_HISTORY', function (data, cb) {
- var hkn = network.historyKeeper;
var crypto = Crypto.createEncryptor(secret.keys);
- // Get the history messages and send them to the iframe
- var parse = function (msg) {
- try {
- return JSON.parse(msg);
- } catch (e) {
- return null;
- }
- };
- var msgs = [];
- var onMsg = function (msg) {
- var parsed = parse(msg);
- if (parsed[0] === 'FULL_HISTORY_END') {
- cb(msgs);
- return;
- }
- if (parsed[0] !== 'FULL_HISTORY') { return; }
- if (parsed[1] && parsed[1].validateKey) { // First message
- return;
- }
- msg = parsed[1][4];
- if (msg) {
- msg = msg.replace(/^cp\|/, '');
- var decryptedMsg = crypto.decrypt(msg, true);
- msgs.push(decryptedMsg);
- }
- };
- network.on('message', onMsg);
- network.sendto(hkn, JSON.stringify(['GET_FULL_HISTORY', secret.channel, secret.keys.validateKey]));
+ Cryptpad.getFullHistory({
+ channel: secret.channel,
+ validateKey: secret.keys.validateKey
+ }, function (encryptedMsgs) {
+ cb(encryptedMsgs.map(function (msg) {
+ return crypto.decrypt(msg, true);
+ }));
+ });
});
sframeChan.on('Q_GET_PAD_ATTRIBUTE', function (data, cb) {
@@ -581,7 +561,7 @@ define([
CpNfOuter.start({
sframeChan: sframeChan,
channel: secret.channel,
- network: cfg.newNetwork || network,
+ padRpc: Cryptpad.padRpc,
validateKey: secret.keys.validateKey || undefined,
readOnly: readOnly,
crypto: Crypto.createEncryptor(secret.keys),
@@ -594,7 +574,7 @@ define([
return;
}
if (readOnly || cfg.noHash) { return; }
- replaceHash(Utils.Hash.getEditHashFromKeys(wc.id, secret.keys));
+ replaceHash(Utils.Hash.getEditHashFromKeys(wc, secret.keys));
}
});
});
diff --git a/www/common/sframe-protocol.js b/www/common/sframe-protocol.js
index b63e516bc..7db1a561a 100644
--- a/www/common/sframe-protocol.js
+++ b/www/common/sframe-protocol.js
@@ -197,4 +197,17 @@ 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,
+ // Store's userObject need to send log messages to inner to display them in the UI
+ 'EV_DRIVE_LOG': true,
+ // Refresh the drive when the drive has changed ('change' or 'remove' events)
+ 'EV_DRIVE_CHANGE': true,
+ 'EV_DRIVE_REMOVE': true,
+
+ // Notifications about connection and disconnection from the network
+ 'EV_NETWORK_DISCONNECT': true,
+ 'EV_NETWORK_RECONNECT': true
});
diff --git a/www/common/userObject.js b/www/common/userObject.js
index 344677ea8..2b9b47d38 100644
--- a/www/common/userObject.js
+++ b/www/common/userObject.js
@@ -4,8 +4,9 @@ define([
'/common/common-hash.js',
'/common/common-realtime.js',
'/common/common-constants.js',
+ '/common/outer/userObject.js',
'/customize/messages.js'
-], function (AppConfig, Util, Hash, Realtime, Constants, Messages) {
+], function (AppConfig, Util, Hash, Realtime, Constants, OuterFO, Messages) {
var module = {};
var ROOT = module.ROOT = "root";
@@ -13,15 +14,10 @@ define([
var TRASH = module.TRASH = "trash";
var TEMPLATE = module.TEMPLATE = "template";
- var clone = function (o) {
- try { return JSON.parse(JSON.stringify(o)); }
- catch (e) { return undefined; }
- };
-
module.init = function (files, config) {
var exp = {};
var pinPads = config.pinPads;
- var loggedIn = config.loggedIn;
+ var sframeChan = config.sframeChan;
var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Constants.storageKey;
var OLD_FILES_DATA = module.OLD_FILES_DATA = exp.OLD_FILES_DATA = Constants.oldStorageKey;
@@ -39,7 +35,7 @@ define([
};
var log = config.log || logging;
var logError = config.logError || logging;
- var debug = config.debug || logging;
+ var debug = exp.debug = config.debug || logging;
var error = exp.error = function() {
exp.fixFiles();
console.error.apply(console, arguments);
@@ -48,6 +44,11 @@ define([
// TODO: workgroup
var workgroup = config.workgroup;
+ if (pinPads) {
+ // Extend "exp" with methods used only outside of the iframe (requires access to store)
+ OuterFO.init(config, exp, files);
+ }
+
/*
* UTILS
@@ -146,26 +147,10 @@ define([
if (type === 'name') { return data.filename; }
return data.filename || data.title || NEW_FILE_NAME;
};
- exp.getPadAttribute = function (href, attr, cb) {
- cb = cb || function () {};
- var id = exp.getIdFromHref(href);
- if (!id) { return void cb(null, undefined); }
- var data = getFileData(id);
- cb(null, clone(data[attr]));
- };
- exp.setPadAttribute = function (href, attr, value, cb) {
- cb = cb || function () {};
- var id = exp.getIdFromHref(href);
- if (!id) { return void cb("E_INVAL_HREF"); }
- if (!attr || !attr.trim()) { return void cb("E_INVAL_ATTR"); }
- var data = getFileData(id);
- data[attr] = clone(value);
- cb(null);
- };
// PATHS
- var comparePath = exp.comparePath = function (a, b) {
+ var comparePath = exp.comparePath = function (a, b) {
if (!a || !b || !Array.isArray(a) || !Array.isArray(b)) { return false; }
if (a.length !== b.length) { return false; }
var result = true;
@@ -473,7 +458,7 @@ define([
* OPERATIONS
*/
- var getAvailableName = function (parentEl, name) {
+ var getAvailableName = exp.getAvailableName = function (parentEl, name) {
if (typeof(parentEl[name]) === "undefined") { return name; }
var newName = name;
var i = 1;
@@ -484,84 +469,17 @@ define([
return newName;
};
- // FILES DATA
- 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();
- }
- if (!pinPads) { return; }
- pinPads([Hash.hrefToHexChannelId(data.href)], function (obj) {
- if (obj && obj.error) { return void cb(obj.error); }
- todo();
- });
- };
- var spliceFileData = exp.removeData = function (id) {
- delete files[FILES_DATA][id];
- };
-
// MOVE
- var pushToTrash = function (name, element, path) {
- var trash = files[TRASH];
- if (typeof(trash[name]) === "undefined") { trash[name] = []; }
- var trashArray = trash[name];
- var trashElement = {
- element: element,
- path: path
- };
- trashArray.push(trashElement);
- };
- var copyElement = function (elementPath, newParentPath) {
- if (comparePath(elementPath, newParentPath)) { return; } // Nothing to do...
- var element = find(elementPath);
- var newParent = find(newParentPath);
-
- // Move to Trash
- if (isPathIn(newParentPath, [TRASH])) {
- if (!elementPath || elementPath.length < 2 || elementPath[0] === TRASH) {
- debug("Can't move an element from the trash to the trash: ", elementPath);
- return;
- }
- var key = elementPath[elementPath.length - 1];
- var elName = isPathIn(elementPath, ['hrefArray']) ? getTitle(element) : key;
- var parentPath = elementPath.slice();
- parentPath.pop();
- pushToTrash(elName, element, parentPath);
- return true;
- }
- // Move to hrefArray
- if (isPathIn(newParentPath, ['hrefArray'])) {
- if (isFolder(element)) {
- log(Messages.fo_moveUnsortedError);
- return;
- } else {
- if (elementPath[0] === newParentPath[0]) { return; }
- var fileRoot = newParentPath[0];
- if (files[fileRoot].indexOf(element) === -1) {
- files[fileRoot].push(element);
+ var move = exp.move = function (paths, newPath, cb) {
+ if (sframeChan) {
+ return void sframeChan.query("Q_DRIVE_USEROBJECT", {
+ cmd: "move",
+ data: {
+ paths: paths,
+ newPath: newPath
}
- return true;
- }
- }
- // Move to root
- var newName = isFile(element) ?
- getAvailableName(newParent, Hash.createChannelId()) :
- isInTrashRoot(elementPath) ?
- elementPath[1] : elementPath.pop();
-
- if (typeof(newParent[newName]) !== "undefined") {
- log(Messages.fo_unavailableName);
- return;
+ }, cb);
}
- newParent[newName] = element;
- return true;
- };
- var move = exp.move = function (paths, newPath, cb) {
// Copy the elements to their new location
var toRemove = [];
paths.forEach(function (p) {
@@ -573,13 +491,21 @@ define([
return;
}
// Try to copy, and if success, remove the element from the old location
- if (copyElement(p.slice(), newPath)) {
+ if (exp.copyElement(p.slice(), newPath)) {
toRemove.push(p);
}
});
exp.delete(toRemove, cb);
};
exp.restore = function (path, cb) {
+ if (sframeChan) {
+ return void sframeChan.query("Q_DRIVE_USEROBJECT", {
+ cmd: "restore",
+ data: {
+ path: path
+ }
+ }, cb);
+ }
if (!isInTrashRoot(path)) { return; }
var parentPath = path.slice();
parentPath.pop();
@@ -589,165 +515,66 @@ define([
// ADD
- var add = exp.add = function (id, path) {
- if (!loggedIn && !config.testMode) { return; }
- var data = files[FILES_DATA][id];
- if (!data || typeof(data) !== "object") { return; }
- var newPath = path, parentEl;
- if (path && !Array.isArray(path)) {
- newPath = decodeURIComponent(path).split(',');
- }
- // Add to href array
- if (path && isPathIn(newPath, ['hrefArray'])) {
- parentEl = find(newPath);
- parentEl.push(id);
- return;
- }
- // Add to root if path is ROOT or if no path
- var filesList = getFiles([ROOT, TRASH, 'hrefArray']);
- if (path && isPathIn(newPath, [ROOT]) || filesList.indexOf(id) === -1) {
- parentEl = find(newPath || [ROOT]);
- if (parentEl) {
- var newName = getAvailableName(parentEl, Hash.createChannelId());
- parentEl[newName] = id;
- return;
- }
- }
- };
exp.addFolder = function (folderPath, name, cb) {
+ if (sframeChan) {
+ return void sframeChan.query("Q_DRIVE_USEROBJECT", {
+ cmd: "addFolder",
+ data: {
+ path: folderPath,
+ name: name
+ }
+ }, cb);
+ }
var parentEl = find(folderPath);
var folderName = getAvailableName(parentEl, name || NEW_FOLDER_NAME);
parentEl[folderName] = {};
var newPath = folderPath.slice();
newPath.push(folderName);
- cb(void 0, {
+ cb({
newPath: newPath
});
};
- // FORGET (move with href not path)
- exp.forget = function (href) {
- var id = getIdFromHref(href);
- if (!id) { return; }
- if (!loggedIn && !config.testMode) {
- // delete permanently
- exp.removePadAttribute(href);
- spliceFileData(id);
- return;
- }
- var paths = findFile(id);
- move(paths, [TRASH]);
- };
-
// 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)
- 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) {
- debug("Deleting pad attribute in the realtime object");
- delete files[key];
- }
- });
- };
- var checkDeletedFiles = function () {
- // Nothing in OLD_FILES_DATA for workgroups
- if (workgroup || (!loggedIn && !config.testMode)) { return; }
-
- var filesList = getFiles([ROOT, 'hrefArray', TRASH]);
- getFiles([FILES_DATA]).forEach(function (id) {
- if (filesList.indexOf(id) === -1) {
- spliceFileData(id);
- }
- });
- };
- var deleteHrefs = function (ids) {
- ids.forEach(function (obj) {
- var idx = files[obj.root].indexOf(obj.id);
- files[obj.root].splice(idx, 1);
- });
- };
- var deleteMultipleTrashRoot = function (roots) {
- roots.forEach(function (obj) {
- var idx = files[TRASH][obj.name].indexOf(obj.el);
- files[TRASH][obj.name].splice(idx, 1);
- });
- };
- var deleteMultiplePermanently = function (paths, nocheck) {
- var hrefPaths = paths.filter(function(x) { return isPathIn(x, ['hrefArray']); });
- var rootPaths = paths.filter(function(x) { return isPathIn(x, [ROOT]); });
- var trashPaths = paths.filter(function(x) { return isPathIn(x, [TRASH]); });
- var allFilesPaths = paths.filter(function(x) { return isPathIn(x, [FILES_DATA]); });
-
- if (!loggedIn && !config.testMode) {
- allFilesPaths.forEach(function (path) {
- var el = find(path);
- if (!el) { return; }
- var id = getIdFromHref(el.href);
- if (!id) { return; }
- spliceFileData(id);
- removePadAttribute(el.href);
- });
- return;
- }
-
- var ids = [];
- hrefPaths.forEach(function (path) {
- var id = find(path);
- ids.push({
- root: path[0],
- id: id
- });
- });
- deleteHrefs(ids);
-
- rootPaths.forEach(function (path) {
- var parentPath = path.slice();
- var key = parentPath.pop();
- var parentEl = find(parentPath);
- delete parentEl[key];
- });
-
- var trashRoot = [];
- trashPaths.forEach(function (path) {
- var parentPath = path.slice();
- var key = parentPath.pop();
- var parentEl = find(parentPath);
- // Trash root: we have array here, we can't just splice with the path otherwise we might break the path
- // of another element in the loop
- if (path.length === 4) {
- trashRoot.push({
- name: path[1],
- el: parentEl
- });
- return;
- }
- // Trash but not root: it's just a tree so remove the key
- delete parentEl[key];
- });
- deleteMultipleTrashRoot(trashRoot);
-
- // In some cases, we want to remove pads from a location without removing them from
- // OLD_FILES_DATA (replaceHref)
- if (!nocheck) { checkDeletedFiles(); }
- };
exp.delete = function (paths, cb, nocheck) {
- deleteMultiplePermanently(paths, nocheck);
+ if (sframeChan) {
+ return void sframeChan.query("Q_DRIVE_USEROBJECT", {
+ cmd: "delete",
+ data: {
+ paths: paths,
+ nocheck: nocheck
+ }
+ }, cb);
+ }
+ exp.deleteMultiplePermanently(paths, nocheck);
if (typeof cb === "function") { cb(); }
};
exp.emptyTrash = function (cb) {
+ if (sframeChan) {
+ return void sframeChan.query("Q_DRIVE_USEROBJECT", {
+ cmd: "emptyTrash"
+ }, cb);
+ }
files[TRASH] = {};
- checkDeletedFiles();
+ exp.checkDeletedFiles();
if(cb) { cb(); }
};
// RENAME
exp.rename = function (path, newName, cb) {
+ if (sframeChan) {
+ console.log(path, newName);
+ return void sframeChan.query("Q_DRIVE_USEROBJECT", {
+ cmd: "rename",
+ data: {
+ path: path,
+ newName: newName
+ }
+ }, cb);
+ }
+ console.log(path, newName);
if (path.length <= 1) {
logError('Renaming `root` is forbidden');
return;
@@ -784,322 +611,6 @@ define([
if (typeof cb === "function") { cb(); }
};
- // REPLACE
- exp.replace = function (o, n) {
- var idO = getIdFromHref(o);
- if (!idO || !isFile(idO)) { return; }
- var data = 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.
- // This is use with setPadTitle when we open a stronger version of a deleted pad
- exp.restoreHref = function (href) {
- var idO = getIdFromHref(href);
-
- if (!idO || !isFile(idO)) { return; }
-
- var paths = findFile(idO);
-
- // Remove all the occurences in the trash
- // If all the occurences are in the trash or no occurence, add the pad to root
- var allInTrash = true;
- paths.forEach(function (p) {
- if (p[0] === TRASH) {
- exp.delete(p, null, true); // 3rd parameter means skip "checkDeletedFiles"
- return;
- }
- allInTrash = false;
- });
- if (allInTrash) {
- add(idO);
- }
- };
-
- /**
- * INTEGRITY CHECK
- */
-
- exp.migrate = function (cb) {
- // Make sure unsorted doesn't exist anymore
- // Note: Unsorted only works with the old structure where pads are href
- // It should be called before the migration code
- var fixUnsorted = function () {
- if (!files[UNSORTED] || !files[OLD_FILES_DATA]) { return; }
- debug("UNSORTED still exists in the object, removing it...");
- var us = files[UNSORTED];
- if (us.length === 0) {
- delete files[UNSORTED];
- return;
- }
- us.forEach(function (el) {
- if (typeof el !== "string") {
- return;
- }
- var data = files[OLD_FILES_DATA].filter(function (x) {
- return x.href === el;
- });
- if (data.length === 0) {
- files[OLD_FILES_DATA].push({
- href: el
- });
- }
- return;
- });
- delete files[UNSORTED];
- };
- // mergeDrive...
- var migrateToNewFormat = function (todo) {
- if (!files[OLD_FILES_DATA]) {
- return void todo();
- }
- try {
- debug("Migrating file system...");
- files.migrate = 1;
- var next = function () {
- var oldData = files[OLD_FILES_DATA].slice();
- if (!files[FILES_DATA]) {
- files[FILES_DATA] = {};
- }
- var newData = files[FILES_DATA];
- //var oldFiles = oldData.map(function (o) { return o.href; });
- oldData.forEach(function (obj) {
- if (!obj || !obj.href) { return; }
- var href = obj.href;
- var id = Util.createRandomInteger();
- var paths = findFile(href);
- var data = obj;
- var key = Hash.createChannelId();
- if (data) {
- newData[id] = data;
- } else {
- newData[id] = {href: href};
- }
- paths.forEach(function (p) {
- var parentPath = p.slice();
- var okey = parentPath.pop(); // get the parent
- var parent = find(parentPath);
- if (isInTrashRoot(p)) {
- parent.element = id;
- newData[id].filename = p[1];
- return;
- }
- if (isPathIn(p, ['hrefArray'])) {
- parent[okey] = id;
- return;
- }
- // else root or trash (not trashroot)
- parent[key] = id;
- newData[id].filename = okey;
- delete parent[okey];
- });
- });
- delete files[OLD_FILES_DATA];
- delete files.migrate;
- console.log('done');
- todo();
- };
- if (exp.rt) {
- exp.rt.sync();
- // TODO
- Realtime.whenRealtimeSyncs(exp.rt, next);
- } else {
- window.setTimeout(next, 1000);
- }
- } catch(e) {
- console.error(e);
- todo();
- }
- };
-
- fixUnsorted();
- migrateToNewFormat(cb);
- };
-
- exp.fixFiles = function () {
- // 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
- // * TRASH: Trash root contains only arrays, each element of the array is an object {element:.., path:..}
- // * OLD_FILES_DATA: - Data (title, cdate, adte) are stored in filesData. filesData contains only href keys linking to object with title, cdate, adate.
- // - 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
- debug("Cleaning file system...");
-
- var before = JSON.stringify(files);
-
- var fixRoot = function (elem) {
- if (typeof(files[ROOT]) !== "object") { debug("ROOT was not an object"); files[ROOT] = {}; }
- var element = elem || files[ROOT];
- for (var el in element) {
- if (!isFile(element[el], true) && !isFolder(element[el])) {
- debug("An element in ROOT was not a folder nor a file. ", element[el]);
- delete element[el];
- continue;
- }
- if (isFolder(element[el])) {
- fixRoot(element[el]);
- continue;
- }
- if (typeof element[el] === "string") {
- // We have an old file (href) which is not in filesData: add it
- var id = Util.createRandomInteger();
- var key = Hash.createChannelId();
- files[FILES_DATA][id] = {href: element[el], filename: el};
- element[key] = id;
- delete element[el];
- }
- if (typeof element[el] === "number") {
- var data = files[FILES_DATA][element[el]];
- if (!data) {
- debug("An element in ROOT doesn't have associated data", element[el], el);
- delete element[el];
- }
- }
- }
- };
- var fixTrashRoot = function () {
- if (typeof(files[TRASH]) !== "object") { debug("TRASH was not an object"); files[TRASH] = {}; }
- var tr = files[TRASH];
- var toClean;
- var addToClean = function (obj, idx, el) {
- if (typeof(obj) !== "object") { toClean.push(idx); return; }
- if (!isFile(obj.element, true) && !isFolder(obj.element)) { toClean.push(idx); return; }
- if (!Array.isArray(obj.path)) { toClean.push(idx); return; }
- if (typeof obj.element === "string") {
- // We have an old file (href) which is not in filesData: add it
- var id = Util.createRandomInteger();
- files[FILES_DATA][id] = {href: obj.element, filename: el};
- obj.element = id;
- }
- if (isFolder(obj.element)) { fixRoot(obj.element); }
- if (typeof obj.element === "number") {
- var data = files[FILES_DATA][obj.element];
- if (!data) {
- debug("An element in TRASH doesn't have associated data", obj.element, el);
- toClean.push(idx);
- }
- }
-
- };
- for (var el in tr) {
- if (!Array.isArray(tr[el])) {
- debug("An element in TRASH root is not an array. ", tr[el]);
- delete tr[el];
- } else if (tr[el].length === 0) {
- debug("Empty array in TRASH root. ", tr[el]);
- delete tr[el];
- } else {
- toClean = [];
- for (var j=0; j
=0; i--) {
- tr[el].splice(toClean[i], 1);
- }
- }
- }
- };
- var fixTemplate = function () {
- if (!Array.isArray(files[TEMPLATE])) { debug("TEMPLATE was not an array"); files[TEMPLATE] = []; }
- files[TEMPLATE] = Util.deduplicateString(files[TEMPLATE].slice());
- var us = files[TEMPLATE];
- var rootFiles = getFiles([ROOT]).slice();
- var toClean = [];
- us.forEach(function (el, idx) {
- if (!isFile(el, true) || rootFiles.indexOf(el) !== -1) {
- toClean.push(el);
- }
- if (typeof el === "string") {
- // We have an old file (href) which is not in filesData: add it
- var id = Util.createRandomInteger();
- files[FILES_DATA][id] = {href: el};
- us[idx] = id;
- }
- if (typeof el === "number") {
- var data = files[FILES_DATA][el];
- if (!data) {
- debug("An element in TEMPLATE doesn't have associated data", el);
- toClean.push(el);
- }
- }
- });
- toClean.forEach(function (el) {
- var idx = us.indexOf(el);
- if (idx !== -1) {
- us.splice(idx, 1);
- }
- });
- };
- var fixFilesData = function () {
- if (typeof files[FILES_DATA] !== "object") { debug("OLD_FILES_DATA was not an object"); files[FILES_DATA] = {}; }
- var fd = files[FILES_DATA];
- var rootFiles = getFiles([ROOT, TRASH, 'hrefArray']);
- var root = find([ROOT]);
- var toClean = [];
- for (var id in fd) {
- id = Number(id);
- var el = fd[id];
- if (!el || typeof(el) !== "object") {
- debug("An element in filesData was not an object.", el);
- toClean.push(id);
- continue;
- }
- if (!el.href) {
- 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); }
- if (!parsed.hash) {
- debug("Removing an element in filesData with a invalid href.", el);
- toClean.push(id);
- continue;
- }
- if (!parsed.type) {
- debug("Removing an element in filesData with a invalid type.", el);
- toClean.push(id);
- continue;
- }
-
- 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();
- root[newName] = id;
- continue;
- }
- }
- toClean.forEach(function (id) {
- spliceFileData(id);
- });
- };
-
- var fixDrive = function () {
- Object.keys(files).forEach(function (key) {
- if (key.slice(0,1) === '/') { delete files[key]; }
- });
- };
-
- fixRoot();
- fixTrashRoot();
- if (!workgroup) {
- fixTemplate();
- fixFilesData();
- }
- fixDrive();
-
- 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");
- return;
- }
- debug("File system was clean");
- };
-
return exp;
};
return module;
diff --git a/www/drive/inner.js b/www/drive/inner.js
index cd25fb99d..2197ba563 100644
--- a/www/drive/inner.js
+++ b/www/drive/inner.js
@@ -185,11 +185,25 @@ define([
isHistoryMode: false,
};
+ var copyObjectValue = function (objRef, objToCopy) {
+ for (var k in objRef) { delete objRef[k]; }
+ $.extend(true, objRef, objToCopy);
+ };
+ var updateObject = function (sframeChan, obj, cb) {
+ sframeChan.query('Q_DRIVE_GETOBJECT', null, function (err, newObj) {
+ copyObjectValue(obj, newObj);
+ cb();
+ });
+ };
+
var andThen = function (common, proxy) {
var files = proxy.drive;
var metadataMgr = common.getMetadataMgr();
+ var sframeChan = common.getSframeChannel();
var priv = metadataMgr.getPrivateData();
var user = metadataMgr.getUserData();
+
+ APP.origin = priv.origin;
var isOwnDrive = function () {
return true; // TODO
};
@@ -198,12 +212,10 @@ define([
};
config.workgroup = isWorkgroup();
config.loggedIn = APP.loggedIn;
+ config.sframeChan = sframeChan;
- APP.origin = priv.origin;
var filesOp = FO.init(files, config);
- filesOp.fixFiles();
-
var error = filesOp.error;
var $tree = APP.$tree = $("#cp-app-drive-tree");
@@ -2056,7 +2068,7 @@ define([
// Display the selected directory into the content part (rightside)
// NOTE: Elements in the trash are not using the same storage structure as the others
// _WORKGROUP_ : do not change the lastOpenedFolder value in localStorage
- var displayDirectory = APP.displayDirectory = function (path, force) {
+ var _displayDirectory = function (path, force) {
APP.hideMenu();
if (!APP.editable) { debug("Read-only mode"); }
if (!appStatus.isReady && !force) { return; }
@@ -2065,7 +2077,7 @@ define([
if (!path || displayedCategories.indexOf(path[0]) === -1) {
log(Messages.categoryError);
currentPath = [ROOT];
- displayDirectory(currentPath);
+ _displayDirectory(currentPath);
return;
}
appStatus.ready(false);
@@ -2092,7 +2104,7 @@ define([
debug("Unable to locate the selected directory: ", path);
var parentPath = path.slice();
parentPath.pop();
- displayDirectory(parentPath, true);
+ _displayDirectory(parentPath, true);
return;
}
if (!isSearch) { delete APP.Search.oldLocation; }
@@ -2220,6 +2232,12 @@ define([
$content.scrollTop(s);
appStatus.ready(true);
};
+ var displayDirectory = APP.displayDirectory = function (path, force) {
+ updateObject(sframeChan, proxy, function () {
+ copyObjectValue(files, proxy.drive);
+ _displayDirectory(path, force);
+ });
+ };
var createTreeElement = function (name, $icon, path, draggable, droppable, collapsable, active) {
var $name = $('', { 'class': 'cp-app-drive-element' }).text(name);
@@ -2851,9 +2869,10 @@ define([
onRefresh.to = window.setTimeout(refresh, 500);
}
};
- proxy.on('change', [], function () {
+
+ sframeChan.on('EV_DRIVE_CHANGE', function (data) {
if (history.isHistoryMode) { return; }
- var path = arguments[2];
+ var path = data.path;
if (path[0] !== 'drive') { return false; }
path = path.slice(1);
var cPath = currentPath.slice();
@@ -2866,25 +2885,20 @@ define([
}
APP.resetTree();
return false;
- }).on('remove', [], function () {
+ });
+ sframeChan.on('EV_DRIVE_REMOVE', function (data) {
if (history.isHistoryMode) { return; }
- var path = arguments[1];
+ var path = data.path;
if (path[0] !== 'drive') { return false; }
path = path.slice(1);
var cPath = currentPath.slice();
if ((filesOp.isPathIn(cPath, ['hrefArray', TRASH]) && cPath[0] === path[0]) ||
(path.length >= cPath.length && filesOp.isSubpath(path, cPath))) {
// Reload after a few to make sure all the change events have been received
- onRefresh.to = window.setTimeout(refresh, 500);
+ onRefresh.refresh();
}
APP.resetTree();
return false;
- }).on('change', ['drive', 'migrate'], function () {
- var path = arguments[2];
- var value = arguments[1];
- if (path[1] === "migrate" && value === 1) {
- if (APP.onDisconnect) { APP.onDisconnect(true); }
- }
});
history.onEnterHistory = function (obj) {
@@ -2922,7 +2936,7 @@ define([
var main = function () {
var common;
- var proxy;
+ var proxy = {};
var readOnly;
nThen(function (waitFor) {
@@ -2942,11 +2956,11 @@ define([
}
metadataMgr.onChange(function () {
if (typeof(metadataMgr.getPrivateData().readOnly) === 'boolean') {
- readOnly = metadataMgr.getPrivateData().readOnly;
+ readOnly = APP.readOnly = metadataMgr.getPrivateData().readOnly;
privReady();
}
});
- }).nThen(function (/* waitFor */) {
+ }).nThen(function (waitFor) {
APP.loggedIn = common.isLoggedIn();
APP.SFCommon = common;
if (!APP.loggedIn) { Feedback.send('ANONYMOUS_DRIVE'); }
@@ -2955,85 +2969,79 @@ define([
common.setTabTitle(Messages.type.drive);
- var listmapConfig = {
+ /*var listmapConfig = {
data: {},
common: common,
logging: false
+ };*/
+
+ var sframeChan = common.getSframeChannel();
+ updateObject(sframeChan, proxy, waitFor());
+ }).nThen(function () {
+ var sframeChan = common.getSframeChannel();
+ var metadataMgr = common.getMetadataMgr();
+ var configTb = {
+ displayed: ['useradmin', 'pageTitle', 'newpad', 'limit'],
+ pageTitle: Messages.type.drive,
+ metadataMgr: metadataMgr,
+ readOnly: readOnly,
+ sfCommon: common,
+ $container: APP.$bar
};
+ var toolbar = APP.toolbar = Toolbar.create(configTb);
- var metadataMgr;
- var rt = APP.rt = Listmap.create(listmapConfig);
- proxy = rt.proxy;
- var onCreate = function (info) {
- APP.realtime = info.realtime;
-
- metadataMgr = common.getMetadataMgr();
-
- var configTb = {
- displayed: ['useradmin', 'pageTitle', 'newpad', 'limit'],
- pageTitle: Messages.type.drive,
- metadataMgr: metadataMgr,
- readOnly: readOnly,
- realtime: info.realtime,
- sfCommon: common,
- $container: APP.$bar
- };
- var toolbar = APP.toolbar = Toolbar.create(configTb);
-
- var $rightside = toolbar.$rightside;
- $rightside.html(''); // Remove the drawer if we don't use it to hide the toolbar
- APP.$displayName = APP.$bar.find('.' + Toolbar.constants.username);
-
- /* add the usage */
- if (APP.loggedIn) {
- common.createUsageBar(function (err, $limitContainer) {
- if (err) { return void logError(err); }
- APP.$limit = $limitContainer;
- }, true);
- }
+ var $rightside = toolbar.$rightside;
+ $rightside.html(''); // Remove the drawer if we don't use it to hide the toolbar
+ APP.$displayName = APP.$bar.find('.' + Toolbar.constants.username);
- /* add a history button */
- APP.histConfig = {
- onLocal: function () {
- proxy.drive = history.currentObj.drive;
- },
- onRemote: function () {},
- setHistory: setHistory,
- applyVal: function (val) {
- var obj = JSON.parse(val || '{}');
- history.currentObj = obj;
- history.onEnterHistory(obj);
- },
- $toolbar: APP.$bar,
- };
+ /* add the usage */
+ if (APP.loggedIn) {
+ common.createUsageBar(function (err, $limitContainer) {
+ if (err) { return void logError(err); }
+ APP.$limit = $limitContainer;
+ }, true);
+ }
+
+ /* add a history button */
+ APP.histConfig = {
+ onLocal: function () {
+ proxy.drive = history.currentObj.drive;
+ },
+ onRemote: function () {},
+ setHistory: setHistory,
+ applyVal: function (val) {
+ var obj = JSON.parse(val || '{}');
+ history.currentObj = obj;
+ history.onEnterHistory(obj);
+ },
+ $toolbar: APP.$bar,
+ };
- // Add a "Burn this drive" button
- if (!APP.loggedIn) {
- APP.$burnThisDrive = common.createButton(null, true).click(function () {
- UI.confirm(Messages.fm_burnThisDrive, function (yes) {
- if (!yes) { return; }
- common.getSframeChannel().event('EV_BURN_ANON_DRIVE');
- }, null, true);
- }).attr('title', Messages.fm_burnThisDriveButton)
- .removeClass('fa-question')
- .addClass('fa-ban');
- }
+ // Add a "Burn this drive" button
+ if (!APP.loggedIn) {
+ APP.$burnThisDrive = common.createButton(null, true).click(function () {
+ UI.confirm(Messages.fm_burnThisDrive, function (yes) {
+ if (!yes) { return; }
+ common.getSframeChannel().event('EV_BURN_ANON_DRIVE');
+ }, null, true);
+ }).attr('title', Messages.fm_burnThisDriveButton)
+ .removeClass('fa-question')
+ .addClass('fa-ban');
+ }
+
+ metadataMgr.onChange(function () {
+ var name = metadataMgr.getUserData().name || Messages.anonymous;
+ APP.$displayName.text(name);
+ });
+
+ $('body').css('display', '');
+ APP.files = proxy;
+ if (!proxy.drive || typeof(proxy.drive) !== 'object') {
+ throw new Error("Corrupted drive");
+ }
+ andThen(common, proxy);
+ UI.removeLoadingScreen();
- metadataMgr.onChange(function () {
- var name = metadataMgr.getUserData().name || Messages.anonymous;
- APP.$displayName.text(name);
- });
- };
- var firstConnection = true;
- var onReady = function () {
- if (!firstConnection) { return; } // TODO fix this issue in listmap
- firstConnection = false;
- $('body').css('display', '');
- APP.files = proxy;
- if (!proxy.drive || typeof(proxy.drive) !== 'object') { proxy.drive = {}; }
- andThen(common, proxy);
- UI.removeLoadingScreen();
- };
var onDisconnect = APP.onDisconnect = function (noAlert) {
setEditable(false);
if (APP.refresh) { APP.refresh(); }
@@ -3047,16 +3055,15 @@ define([
UI.findOKButton().click();
};
- proxy.on('create', function (info) {
- onCreate(info);
- }).on('ready', function () {
- onReady();
+ sframeChan.on('EV_DRIVE_LOG', function (msg) {
+ UI.log(msg);
});
- proxy.on('disconnect', function () {
+ sframeChan.on('EV_NETWORK_DISCONNECT', function () {
onDisconnect();
});
- proxy.on('reconnect', function (info) {
- onReconnect(info);
+ sframeChan.on('EV_NETWORK_RECONNECT', function (data) {
+ // data.myId;
+ onReconnect(data);
});
common.onLogout(function () { setEditable(false); });
});
diff --git a/www/drive/main.js b/www/drive/main.js
index de668fc4d..552a6a0c6 100644
--- a/www/drive/main.js
+++ b/www/drive/main.js
@@ -48,14 +48,35 @@ define([
Utils.LocalStore.clearThumbnail();
window.location.reload();
});
+ sframeChan.on('Q_DRIVE_USEROBJECT', function (data, cb) {
+ Cryptpad.userObjectCommand(data, cb);
+ });
+ sframeChan.on('Q_DRIVE_GETOBJECT', function (data, cb) {
+ Cryptpad.getUserObject(function (obj) {
+ cb(obj);
+ });
+ });
+ Cryptpad.onNetworkDisconnect.reg(function () {
+ sframeChan.event('EV_NETWORK_DISCONNECT');
+ });
+ Cryptpad.onNetworkReconnect.reg(function (data) {
+ sframeChan.event('EV_NETWORK_RECONNECT', data);
+ });
+ Cryptpad.drive.onLog.reg(function (msg) {
+ sframeChan.event('EV_DRIVE_LOG', msg);
+ });
+ Cryptpad.drive.onChange.reg(function (data) {
+ sframeChan.event('EV_DRIVE_CHANGE', data);
+ });
+ Cryptpad.drive.onRemove.reg(function (data) {
+ sframeChan.event('EV_DRIVE_REMOVE', data);
+ });
};
- //Netflux.connect(NetConfig.getWebsocketURL()).then(function (network) {
- SFCommonO.start({
- getSecrets: getSecrets,
- //newNetwork: network,
- noHash: true,
- addRpc: addRpc
- });
- //}, function (err) { console.error(err); });
+ SFCommonO.start({
+ getSecrets: getSecrets,
+ noHash: true,
+ driveEvents: true,
+ addRpc: addRpc
+ });
});
});
diff --git a/www/drive/tests.js b/www/drive/tests.js
index 7132fd8a7..a5fec1145 100644
--- a/www/drive/tests.js
+++ b/www/drive/tests.js
@@ -362,7 +362,7 @@ define([
return cb();
}
var path;
- fo.addFolder(["root", "Folder2"], "subsub", function (e, o) { path = o.newPath; });
+ fo.addFolder(["root", "Folder2"], "subsub", function (o) { path = o.newPath; });
if (!files.root.Folder2.subsub || path.length !== 3) {
console.log("DRIVE operations: add folder");
return cb();
diff --git a/www/worker/app-worker.less b/www/worker/app-worker.less
new file mode 100644
index 000000000..c21879f4f
--- /dev/null
+++ b/www/worker/app-worker.less
@@ -0,0 +1,40 @@
+@import (once) "../../customize/src/less2/include/browser.less";
+@import (once) "../../customize/src/less2/include/toolbar.less";
+@import (once) "../../customize/src/less2/include/markdown.less";
+@import (once) '../../customize/src/less2/include/fileupload.less';
+@import (once) '../../customize/src/less2/include/alertify.less';
+//@import (once) '../../customize/src/less/mixins.less';
+//@import (once) '../../customize/src/less/variables.less";
+
+@import (once) '../../customize/src/less2/include/avatar.less';
+
+
+.toolbar_main();
+.fileupload_main();
+.alertify_main();
+
+// body
+&.cp-app-worker {
+ display: flex;
+ flex-flow: column;
+
+ #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-worker-container {
+ display: flex;
+ flex: 1;
+ flex-flow: column;
+ padding: 20px;
+ align-items: center;
+ background-color: lighten(@colortheme_todo-bg, 15%);
+ min-height: 0;
+ }
+}
+
diff --git a/www/worker/index.html b/www/worker/index.html
new file mode 100644
index 000000000..e3f7eacc4
--- /dev/null
+++ b/www/worker/index.html
@@ -0,0 +1,38 @@
+
+
+
+ CryptPad
+
+
+
+
+
+
+
+