|
|
|
define([
|
|
|
|
'/customize/application_config.js',
|
|
|
|
'/common/common-feedback.js',
|
|
|
|
'/common/common-hash.js',
|
|
|
|
'/common/common-util.js',
|
|
|
|
'/common/common-messaging.js',
|
|
|
|
'/common/cryptget.js',
|
|
|
|
'/common/outer/mailbox.js',
|
|
|
|
'/customize/messages.js',
|
|
|
|
'/common/common-realtime.js',
|
|
|
|
'/bower_components/nthen/index.js',
|
|
|
|
'/bower_components/chainpad-crypto/crypto.js',
|
|
|
|
], function (AppConfig, Feedback, Hash, Util, Messaging, Crypt, Mailbox, Messages, Realtime, nThen, Crypto) {
|
|
|
|
// Start migration check
|
|
|
|
// Versions:
|
|
|
|
// 1: migrate pad attributes
|
|
|
|
// 2: migrate indent settings (codemirror)
|
|
|
|
|
|
|
|
return function (userObject, cb, progress, store) {
|
|
|
|
var version = userObject.version || 0;
|
|
|
|
|
|
|
|
nThen(function () {
|
|
|
|
// DEPRECATED
|
|
|
|
// Migration 1: pad attributes moved to filesData
|
|
|
|
var migratePadAttributesToData = function () {
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
if (version < 1) {
|
|
|
|
migratePadAttributesToData();
|
|
|
|
}
|
|
|
|
}).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;
|
|
|
|
}
|
|
|
|
}).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;
|
|
|
|
}
|
|
|
|
}).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;
|
|
|
|
}
|
|
|
|
}).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;
|
|
|
|
}
|
|
|
|
}).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) {
|
|
|
|
// Already migrated
|
|
|
|
return void progress(7, Math.round(100*i/padsLength));
|
|
|
|
}
|
|
|
|
if (el.href.indexOf('#') === -1) {
|
|
|
|
// Encrypted href: 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 () {
|
|
|
|
// Migration 8: remove duplicate entries in proxy.FS_hashes (list of migrated anon drives)
|
|
|
|
var fixDuplicate = function () {
|
|
|
|
userObject.FS_hashes = Util.deduplicateString(userObject.FS_hashes || []);
|
|
|
|
};
|
|
|
|
if (version < 8) {
|
|
|
|
fixDuplicate();
|
|
|
|
Feedback.send('Migrate-8', true);
|
|
|
|
userObject.version = version = 8;
|
|
|
|
}
|
|
|
|
}).nThen(function () {
|
|
|
|
// Migration 9: send our mailbox channel to existing friends
|
|
|
|
var migrateFriends = function () {
|
|
|
|
var network = store.network;
|
|
|
|
var channels = {};
|
|
|
|
var ctx = {
|
|
|
|
store: store
|
|
|
|
};
|
|
|
|
var myData = Messaging.createData(userObject);
|
|
|
|
|
|
|
|
var close = function (chan) {
|
|
|
|
var channel = channels[chan];
|
|
|
|
if (!channel) { return; }
|
|
|
|
try {
|
|
|
|
channel.wc.leave();
|
|
|
|
} catch (e) {}
|
|
|
|
delete channels[chan];
|
|
|
|
};
|
|
|
|
|
|
|
|
var onDirectMessage = function (msg, sender) {
|
|
|
|
if (sender !== network.historyKeeper) { return; }
|
|
|
|
var parsed = JSON.parse(msg);
|
|
|
|
|
|
|
|
// Metadata msg? we don't care
|
|
|
|
if ((parsed.validateKey || parsed.owners) && parsed.channel) { return; }
|
|
|
|
|
|
|
|
// End of history message, "onReady"
|
|
|
|
if (parsed.channel && channels[parsed.channel]) {
|
|
|
|
// History cleared while we were offline
|
|
|
|
// ==> we asked for an invalid last known hash
|
|
|
|
if (parsed.error && parsed.error === "EINVAL") {
|
|
|
|
var histMsg = ['GET_HISTORY', parsed.channel, {}];
|
|
|
|
network.sendto(network.historyKeeper, JSON.stringify(histMsg))
|
|
|
|
.then(function () {}, function () {});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// End of history
|
|
|
|
if (parsed.state && parsed.state === 1) {
|
|
|
|
// Channel is ready and we didn't receive their mailbox channel: send our channel
|
|
|
|
myData.channel = parsed.channel;
|
|
|
|
var updateMsg = ['UPDATE', myData.curvePublic, +new Date(), myData];
|
|
|
|
var cryptMsg = channels[parsed.channel].encrypt(JSON.stringify(updateMsg));
|
|
|
|
channels[parsed.channel].wc.bcast(cryptMsg).then(function () {}, function (err) {
|
|
|
|
console.error("Can't migrate this friend", channels[parsed.channel].friend, err);
|
|
|
|
});
|
|
|
|
close(parsed.channel);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if (parsed.channel) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// History message: we only care about "UPDATE" messages
|
|
|
|
var chan = parsed[3];
|
|
|
|
if (!chan || !channels[chan]) { return; }
|
|
|
|
var channel = channels[chan];
|
|
|
|
var msgIn = channel.decrypt(parsed[4]);
|
|
|
|
var parsedMsg = JSON.parse(msgIn);
|
|
|
|
if (parsedMsg[0] === 'UPDATE') {
|
|
|
|
if (parsedMsg[1] === myData.curvePublic) { return; }
|
|
|
|
var data = parsedMsg[3];
|
|
|
|
// If it doesn't contain the mailbox channel, ignore the message
|
|
|
|
if (!data.notifications) { return; }
|
|
|
|
// Otherwise we know their channel, we can send them our own
|
|
|
|
channel.friend.notifications = data.notifications;
|
|
|
|
myData.channel = chan;
|
|
|
|
Mailbox.sendTo(ctx, 'UPDATE_DATA', myData, {
|
|
|
|
channel: data.notifications,
|
|
|
|
curvePublic: data.curvePublic
|
|
|
|
}, function (obj) {
|
|
|
|
if (obj && obj.error) { return void console.error(obj); }
|
|
|
|
console.log('friend migrated', channel.friend);
|
|
|
|
});
|
|
|
|
close(chan);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
network.on('message', function(msg, sender) {
|
|
|
|
try {
|
|
|
|
onDirectMessage(msg, sender);
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
var friends = userObject.friends || {};
|
|
|
|
Object.keys(friends).forEach(function (curve) {
|
|
|
|
if (curve.length !== 44) { return; }
|
|
|
|
var friend = friends[curve];
|
|
|
|
|
|
|
|
// Check if it is already a "new" friend
|
|
|
|
if (friend.notifications) { return; }
|
|
|
|
|
|
|
|
/** Old friend:
|
|
|
|
* 1. Open the messenger channel
|
|
|
|
* 2. Check if they sent us their mailbox channel
|
|
|
|
* 3.a. Yes ==> sent them a mail containing our mailbox channel
|
|
|
|
* 3.b. No ==> post our mailbox data to the messenger channel
|
|
|
|
*/
|
|
|
|
network.join(friend.channel).then(function (wc) {
|
|
|
|
var keys = Crypto.Curve.deriveKeys(friend.curvePublic, userObject.curvePrivate);
|
|
|
|
var encryptor = Crypto.Curve.createEncryptor(keys);
|
|
|
|
channels[friend.channel] = {
|
|
|
|
wc: wc,
|
|
|
|
friend: friend,
|
|
|
|
decrypt: encryptor.decrypt,
|
|
|
|
encrypt: encryptor.encrypt
|
|
|
|
};
|
|
|
|
var cfg = {
|
|
|
|
lastKnownHash: friend.lastKnownHash
|
|
|
|
};
|
|
|
|
var msg = ['GET_HISTORY', friend.channel, cfg];
|
|
|
|
network.sendto(network.historyKeeper, JSON.stringify(msg))
|
|
|
|
.then(function () {}, function (err) {
|
|
|
|
console.error("Can't migrate this friend", friend, err);
|
|
|
|
});
|
|
|
|
}, function (err) {
|
|
|
|
console.error("Can't migrate this friend", friend, err);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
if (version < 9) {
|
|
|
|
migrateFriends();
|
|
|
|
Feedback.send('Migrate-9', true);
|
|
|
|
userObject.version = version = 9;
|
|
|
|
}
|
|
|
|
}).nThen(function (waitFor) {
|
|
|
|
// Migration 10: deprecate todo
|
|
|
|
var fixTodo = function () {
|
|
|
|
var h = store.proxy.todo;
|
|
|
|
if (!h) { return; }
|
|
|
|
var next = waitFor(function () {
|
|
|
|
Feedback.send('Migrate-10', true);
|
|
|
|
userObject.version = version = 10;
|
|
|
|
});
|
|
|
|
var old;
|
|
|
|
var opts = {
|
|
|
|
network: store.network,
|
|
|
|
initialState: '{}',
|
|
|
|
metadata: {
|
|
|
|
owners: store.proxy.edPublic ? [store.proxy.edPublic] : []
|
|
|
|
}
|
|
|
|
};
|
|
|
|
nThen(function (w) {
|
|
|
|
Crypt.get(h, w(function (err, val) {
|
|
|
|
if (err || !val) {
|
|
|
|
w.abort();
|
|
|
|
next();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
old = JSON.parse(val);
|
|
|
|
} catch (e) {} // We will abort in the next step in case of error
|
|
|
|
}), opts);
|
|
|
|
}).nThen(function (w) {
|
|
|
|
if (!old || typeof(old) !== "object") {
|
|
|
|
w.abort();
|
|
|
|
next();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var k = {
|
|
|
|
content: {
|
|
|
|
data: {
|
|
|
|
"1": {
|
|
|
|
id: "1",
|
|
|
|
color: 'color6',
|
|
|
|
item: [],
|
|
|
|
title: Messages.kanban_todo
|
|
|
|
},
|
|
|
|
"2": {
|
|
|
|
id: "2",
|
|
|
|
color: 'color3',
|
|
|
|
item: [],
|
|
|
|
title: Messages.kanban_working
|
|
|
|
},
|
|
|
|
"3": {
|
|
|
|
id: "3",
|
|
|
|
color: 'color5',
|
|
|
|
item: [],
|
|
|
|
title: Messages.kanban_done
|
|
|
|
},
|
|
|
|
},
|
|
|
|
items: {},
|
|
|
|
list: [1, 2, 3]
|
|
|
|
},
|
|
|
|
metadata: {
|
|
|
|
title: Messages.type.todo,
|
|
|
|
defaultTitle: Messages.type.todo,
|
|
|
|
type: "kanban"
|
|
|
|
}
|
|
|
|
};
|
|
|
|
var i = 4;
|
|
|
|
var items = false;
|
|
|
|
(old.order || []).forEach(function (key) {
|
|
|
|
var data = old.data[key];
|
|
|
|
if (!data || !data.task) { return; }
|
|
|
|
items = true;
|
|
|
|
var column = data.state ? '3' : '1';
|
|
|
|
k.content.data[column].item.push(i);
|
|
|
|
k.content.items[i] = {
|
|
|
|
id: i,
|
|
|
|
title: data.task
|
|
|
|
};
|
|
|
|
i++;
|
|
|
|
});
|
|
|
|
if (!items) {
|
|
|
|
w.abort();
|
|
|
|
next();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var newH = Hash.createRandomHash('kanban');
|
|
|
|
var secret = Hash.getSecrets('kanban', newH);
|
|
|
|
var oldSecret = Hash.getSecrets('todo', h);
|
|
|
|
Crypt.put(newH, JSON.stringify(k), w(function (err) {
|
|
|
|
if (err) {
|
|
|
|
w.abort();
|
|
|
|
next();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (store.rpc) {
|
|
|
|
store.rpc.pin([secret.channel], function () {
|
|
|
|
// Try to pin and ignore errors...
|
|
|
|
// Todo won't be available anyway so keep your unpinned kanban
|
|
|
|
});
|
|
|
|
store.rpc.unpin([oldSecret.channel], function () {
|
|
|
|
// Try to unpin and ignore errors...
|
|
|
|
});
|
|
|
|
}
|
|
|
|
var href = Hash.hashToHref(newH, 'kanban');
|
|
|
|
store.manager.addPad(['root'], {
|
|
|
|
title: Messages.type.todo,
|
|
|
|
owners: opts.metadata.owners,
|
|
|
|
channel: secret.channel,
|
|
|
|
href: href,
|
|
|
|
roHref: Hash.hashToHref(Hash.getViewHashFromKeys(secret), 'kanban'),
|
|
|
|
atime: +new Date(),
|
|
|
|
ctime: +new Date()
|
|
|
|
}, w(function (e) {
|
|
|
|
if (e) { return void console.error(e); }
|
|
|
|
delete store.proxy.todo;
|
|
|
|
var myData = Messaging.createData(userObject);
|
|
|
|
var ctx = { store: store };
|
|
|
|
Mailbox.sendTo(ctx, 'MOVE_TODO', {
|
|
|
|
user: myData,
|
|
|
|
href: href,
|
|
|
|
}, {
|
|
|
|
channel: myData.notifications,
|
|
|
|
curvePublic: myData.curvePublic
|
|
|
|
}, function (obj) {
|
|
|
|
if (obj && obj.error) { return void console.error(obj); }
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
}), opts);
|
|
|
|
}).nThen(function () {
|
|
|
|
next();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
if (version < 10) {
|
|
|
|
fixTodo();
|
|
|
|
}
|
|
|
|
}).nThen(function (waitFor) {
|
|
|
|
if (version >= 11) { return; }
|
|
|
|
// Migration 11: alert users of safe links as the new default
|
|
|
|
|
|
|
|
var done = function () {
|
|
|
|
Feedback.send('Migrate-11', true);
|
|
|
|
userObject.version = version = 11;
|
|
|
|
};
|
|
|
|
|
|
|
|
/* userObject.settings.security.unsafeLinks
|
|
|
|
undefined => the user has never touched it
|
|
|
|
false => the user has explicitly enabled "safe links"
|
|
|
|
true => the user has explicitly disabled "safe links"
|
|
|
|
*/
|
|
|
|
var unsafeLinks = Util.find(userObject, [ 'settings', 'security', 'unsafeLinks' ]);
|
|
|
|
if (unsafeLinks !== undefined) { return void done(); }
|
|
|
|
|
|
|
|
var ctx = {
|
|
|
|
store: store,
|
|
|
|
};
|
|
|
|
var myData = Messaging.createData(userObject);
|
|
|
|
|
|
|
|
Mailbox.sendTo(ctx, 'SAFE_LINKS_DEFAULT', {
|
|
|
|
user: myData,
|
|
|
|
}, {
|
|
|
|
channel: myData.notifications,
|
|
|
|
curvePublic: myData.curvePublic
|
|
|
|
}, waitFor(function (obj) {
|
|
|
|
if (obj && obj.error) { return void console.error(obj); }
|
|
|
|
done();
|
|
|
|
}));
|
|
|
|
/*}).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 () {
|
|
|
|
Realtime.whenRealtimeSyncs(store.realtime, Util.mkAsync(Util.bake(cb)));
|
|
|
|
});
|
|
|
|
};
|
|
|
|
});
|