define([
'jquery',
'/api/config',
'/customize/messages.js',
'/common/fsStore.js',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-interface.js',
'/common/common-history.js',
'/common/clipboard.js',
'/common/pinpad.js',
'/customize/application_config.js'
], function ($, Config, Messages, Store, Util, Hash, UI, History, Clipboard, Pinpad, AppConfig) {
/* This file exposes functionality which is specific to Cryptpad, but not to
any particular pad type. This includes functions for committing metadata
about pads to your local storage for future use and improved usability.
Additionally, there is some basic functionality for import/export.
*/
var common = window.Cryptpad = {
Messages: Messages,
Clipboard: Clipboard
};
// constants
var userHashKey = common.userHashKey = 'User_hash';
var userNameKey = common.userNameKey = 'User_name';
var fileHashKey = common.fileHashKey = 'FS_hash';
var displayNameKey = common.displayNameKey = 'cryptpad.username';
var newPadNameKey = common.newPadNameKey = "newPadName";
var newPadPathKey = common.newPadPathKey = "newPadPath";
var storageKey = common.storageKey = 'CryptPad_RECENTPADS';
var PINNING_ENABLED = AppConfig.enablePinning;
var store;
var rpc;
// import UI elements
var findCancelButton = common.findCancelButton = UI.findCancelButton;
var findOKButton = common.findOKButton = UI.findOKButton;
var listenForKeys = common.listenForKeys = UI.listenForKeys;
var stopListening = common.stopListening = UI.stopListening;
common.prompt = UI.prompt;
common.confirm = UI.confirm;
common.alert = UI.alert;
common.log = UI.log;
common.warn = UI.warn;
common.spinner = UI.spinner;
common.addLoadingScreen = UI.addLoadingScreen;
common.removeLoadingScreen = UI.removeLoadingScreen;
common.errorLoadingScreen = UI.errorLoadingScreen;
// import common utilities for export
var find = common.find = Util.find;
var fixHTML = common.fixHTML = Util.fixHTML;
var hexToBase64 = common.hexToBase64 = Util.hexToBase64;
var base64ToHex = common.base64ToHex = Util.base64ToHex;
var deduplicateString = common.deduplicateString = Util.deduplicateString;
var uint8ArrayToHex = common.uint8ArrayToHex = Util.uint8ArrayToHex;
var replaceHash = common.replaceHash = Util.replaceHash;
var getHash = common.getHash = Util.getHash;
var fixFileName = common.fixFileName = Util.fixFileName;
common.bytesToMegabytes = Util.bytesToMegabytes;
common.bytesToKilobytes = Util.bytesToKilobytes;
// import hash utilities for export
var createRandomHash = common.createRandomHash = Hash.createRandomHash;
var parsePadUrl = common.parsePadUrl = Hash.parsePadUrl;
var isNotStrongestStored = common.isNotStrongestStored = Hash.isNotStrongestStored;
var hrefToHexChannelId = common.hrefToHexChannelId = Hash.hrefToHexChannelId;
var parseHash = common.parseHash = Hash.parseHash;
var getRelativeHref = common.getRelativeHref = Hash.getRelativeHref;
common.getEditHashFromKeys = Hash.getEditHashFromKeys;
common.getViewHashFromKeys = Hash.getViewHashFromKeys;
common.getSecrets = Hash.getSecrets;
common.getHashes = Hash.getHashes;
common.createChannelId = Hash.createChannelId;
common.findWeaker = Hash.findWeaker;
common.findStronger = Hash.findStronger;
// History
common.getHistory = function (config) { return History.create(common, config); };
var getStore = common.getStore = function () {
if (store) { return store; }
throw new Error("Store is not ready!");
};
var getProxy = common.getProxy = function () {
if (store && store.getProxy()) {
return store.getProxy().proxy;
}
};
var getNetwork = common.getNetwork = function () {
if (store) {
if (store.getProxy() && store.getProxy().info) {
return store.getProxy().info.network;
}
}
return;
};
var feedback = common.feedback = function (action, force) {
if (force !== true) {
if (!action) { return; }
try {
if (!getStore().getProxy().proxy.allowUserFeedback) { return; }
} catch (e) { return void console.error(e); }
}
var href = '/common/feedback.html?' + action + '=' + (+new Date());
console.log('[feedback] %s', href);
$.ajax({
type: "HEAD",
url: href,
});
};
var reportAppUsage = common.reportAppUsage = function () {
var pattern = window.location.pathname.split('/')
.filter(function (x) { return x; }).join('.');
feedback(pattern);
};
var getUid = common.getUid = function () {
if (store && store.getProxy() && store.getProxy().proxy) {
return store.getProxy().proxy.uid;
}
};
var getRealtime = common.getRealtime = function () {
if (store && store.getProxy() && store.getProxy().info) {
return store.getProxy().info.realtime;
}
return;
};
var whenRealtimeSyncs = common.whenRealtimeSyncs = function (realtime, cb) {
realtime.sync();
window.setTimeout(function () {
if (realtime.getAuthDoc() === realtime.getUserDoc()) {
return void cb();
}
realtime.onSettle(function () {
cb();
});
}, 0);
};
var getWebsocketURL = common.getWebsocketURL = function () {
if (!Config.websocketPath) { return Config.websocketURL; }
var path = Config.websocketPath;
if (/^ws{1,2}:\/\//.test(path)) { return path; }
var protocol = window.location.protocol.replace(/http/, 'ws');
var host = window.location.host;
var url = protocol + '//' + host + path;
return url;
};
var login = common.login = function (hash, name, cb) {
if (!hash) { throw new Error('expected a user hash'); }
if (!name) { throw new Error('expected a user name'); }
localStorage.setItem(userHashKey, hash);
localStorage.setItem(userNameKey, name);
if (cb) { cb(); }
};
var eraseTempSessionValues = common.eraseTempSessionValues = function () {
// delete sessionStorage values that might have been left over
// from the main page's /user redirect
[
'login',
'login_user',
'login_pass',
'login_rmb',
'register'
].forEach(function (k) {
delete sessionStorage[k];
});
};
var logoutHandlers = [];
var logout = common.logout = function (cb) {
[
userNameKey,
userHashKey,
].forEach(function (k) {
sessionStorage.removeItem(k);
localStorage.removeItem(k);
delete localStorage[k];
delete sessionStorage[k];
});
// Make sure we have an FS_hash in localStorage before reloading all the tabs
// so that we don't end up with tabs using different anon hashes
if (!localStorage[fileHashKey]) {
localStorage[fileHashKey] = common.createRandomHash();
}
eraseTempSessionValues();
logoutHandlers.forEach(function (h) {
if (typeof (h) === "function") { h(); }
});
if (cb) { cb(); }
};
var onLogout = common.onLogout = function (h) {
if (typeof (h) !== "function") { return; }
if (logoutHandlers.indexOf(h) !== -1) { return; }
logoutHandlers.push(h);
};
var getUserHash = common.getUserHash = function () {
var hash;
[sessionStorage, localStorage].some(function (s) {
var h = s[userHashKey];
if (h) { return (hash = h); }
});
return hash;
};
var isLoggedIn = common.isLoggedIn = function () {
return typeof getUserHash() === "string";
};
var hasSigningKeys = common.hasSigningKeys = function (proxy) {
return typeof(proxy) === 'object' &&
typeof(proxy.edPrivate) === 'string' &&
typeof(proxy.edPublic) === 'string';
};
common.isArray = $.isArray;
/*
* localStorage formatting
*/
/*
the first time this gets called, your local storage will migrate to a
new format. No more indices for values, everything is named now.
* href
* atime (access time)
* title
* ??? // what else can we put in here?
*/
var checkObjectData = function (pad, cb) {
if (!pad.ctime) { pad.ctime = pad.atime; }
if (/^https*:\/\//.test(pad.href)) {
pad.href = common.getRelativeHref(pad.href);
}
var parsed = common.parsePadUrl(pad.href);
if (!parsed || !parsed.hash) { return; }
if (typeof(cb) === 'function') {
cb(parsed);
}
if (!pad.title) {
pad.title = common.getDefaultname(parsed);
}
return parsed.hash;
};
// Migrate from legacy store (localStorage)
var migrateRecentPads = common.migrateRecentPads = function (pads) {
return pads.map(function (pad) {
var hash;
if (Array.isArray(pad)) { // TODO DEPRECATE_F
var href = pad[0];
href.replace(/\#(.*)$/, function (a, h) {
hash = h;
});
return {
href: pad[0],
atime: pad[1],
title: pad[2] || hash && hash.slice(0,8),
ctime: pad[1],
};
} else if (pad && typeof(pad) === 'object') {
hash = checkObjectData(pad);
if (!hash || !common.parseHash(hash)) { return; }
return pad;
} else {
console.error("[Cryptpad.migrateRecentPads] pad had unexpected value");
console.log(pad);
return;
}
}).filter(function (x) { return x; });
};
// Remove everything from RecentPads that is not an object and check the objects
var checkRecentPads = common.checkRecentPads = function (pads) {
pads.forEach(function (pad, i) {
if (pad && typeof(pad) === 'object') {
var hash = checkObjectData(pad);
if (!hash || !common.parseHash(hash)) { return; }
return pad;
}
console.error("[Cryptpad.migrateRecentPads] pad had unexpected value");
getStore().removeData(i);
});
};
// Get the pads from localStorage to migrate them to the object store
var getLegacyPads = common.getLegacyPads = function (cb) {
require(['/customize/store.js'], function(Legacy) { // TODO DEPRECATE_F
Legacy.ready(function (err, legacy) {
if (err) { cb(err, null); return; }
legacy.get(storageKey, function (err2, recentPads) {
if (err2) { cb(err2, null); return; }
if (Array.isArray(recentPads)) {
feedback('MIGRATE_LEGACY_STORE');
cb(void 0, migrateRecentPads(recentPads));
return;
}
cb(void 0, []);
});
});
});
};
// Create untitled documents when no name is given
var getDefaultName = common.getDefaultName = function (parsed) {
var type = parsed.type;
var untitledIndex = 1;
var name = (Messages.type)[type] + ' - ' + new Date().toString().split(' ').slice(0,4).join(' ');
return name;
};
var isDefaultName = common.isDefaultName = function (parsed, title) {
var name = getDefaultName(parsed);
return title === name;
};
var makePad = function (href, title) {
var now = +new Date();
return {
href: href,
atime: now,
ctime: now,
title: title || window.location.hash.slice(1, 9),
};
};
/* Sort pads according to how recently they were accessed */
var mostRecent = common.mostRecent = function (a, b) {
return new Date(b.atime).getTime() - new Date(a.atime).getTime();
};
// STORAGE
var setPadAttribute = common.setPadAttribute = function (attr, value, cb) {
getStore().setDrive([getHash(), attr].join('.'), value, function (err, data) {
cb(err, data);
});
};
var setAttribute = common.setAttribute = function (attr, value, cb) {
getStore().set(["cryptpad", attr].join('.'), value, function (err, data) {
cb(err, data);
});
};
var setLSAttribute = common.setLSAttribute = function (attr, value) {
localStorage[attr] = value;
};
// STORAGE
var getPadAttribute = common.getPadAttribute = function (attr, cb) {
getStore().getDrive([getHash(), attr].join('.'), function (err, data) {
cb(err, data);
});
};
var getAttribute = common.getAttribute = function (attr, cb) {
getStore().get(["cryptpad", attr].join('.'), function (err, data) {
cb(err, data);
});
};
var getLSAttribute = common.getLSAttribute = function (attr) {
return localStorage[attr];
};
// STORAGE - TEMPLATES
var listTemplates = common.listTemplates = function (type) {
var allTemplates = getStore().listTemplates();
if (!type) { return allTemplates; }
var templates = allTemplates.filter(function (f) {
var parsed = parsePadUrl(f.href);
return parsed.type === type;
});
return templates;
};
var addTemplate = common.addTemplate = function (data) {
getStore().pushData(data);
getStore().addPad(data.href, ['template']);
};
var isTemplate = common.isTemplate = function (href) {
var rhref = getRelativeHref(href);
var templates = listTemplates();
return templates.some(function (t) {
return t.href === rhref;
});
};
var selectTemplate = common.selectTemplate = function (type, rt, Crypt) {
if (!AppConfig.enableTemplates) { return; }
var temps = listTemplates(type);
if (temps.length === 0) { return; }
var $content = $('