Merge branch 'staging' into team

pull/1/head
yflory 5 years ago
commit 15387af020

@ -8,6 +8,7 @@ const Crypto = require('crypto');
const Once = require("./lib/once");
const Meta = require("./lib/metadata");
const WriteQueue = require("./lib/write-queue");
const BatchRead = require("./lib/batch-read");
let Log;
const now = function () { return (new Date()).getTime(); };
@ -231,7 +232,7 @@ module.exports.create = function (cfg) {
as an added bonus:
if the channel exists but its index does not then it caches the index
*/
const indexQueues = {};
const batchIndexReads = BatchRead("HK_GET_INDEX");
const getIndex = (ctx, channelName, cb) => {
const chan = ctx.channels[channelName];
// if there is a channel in memory and it has an index cached, return it
@ -242,40 +243,14 @@ module.exports.create = function (cfg) {
});
}
// if a call to computeIndex is already in progress for this channel
// then add the callback for the latest invocation to the queue
// and wait for it to complete
if (Array.isArray(indexQueues[channelName])) {
indexQueues[channelName].push(cb);
return;
}
// otherwise, make a queue for any 'getIndex' calls made before the following 'computeIndex' call completes
var queue = indexQueues[channelName] = (indexQueues[channelName] || [cb]);
batchIndexReads(channelName, cb, function (done) {
computeIndex(channelName, (err, ret) => {
if (!Array.isArray(queue)) {
// something is very wrong if there's no callback array
return void Log.error("E_INDEX_NO_CALLBACK", channelName);
}
// clean up the queue that you're about to handle, but keep a local copy
delete indexQueues[channelName];
// this is most likely an unrecoverable filesystem error
if (err) {
// call back every pending function with the error
return void queue.forEach(function (_cb) {
_cb(err);
});
}
if (err) { return void done(err); }
// cache the computed result if possible
if (chan) { chan.index = ret; }
// call back every pending function with the result
queue.forEach(function (_cb) {
_cb(void 0, ret);
// return
done(void 0, ret);
});
});
};

@ -0,0 +1,61 @@
/*
## Purpose
To avoid running expensive IO or computation concurrently.
If the result of IO or computation is requested while an identical request
is already in progress, wait until the first one completes and provide its
result to every routine that requested it.
## Usage
Provide:
1. a named key for the computation or resource,
2. a callback to handle the result
3. an implementation which calls back with the result
```
var batch = Batch();
var read = function (path, cb) {
batch(path, cb, function (done) {
console.log("reading %s", path);
fs.readFile(path, 'utf8', done);
});
};
read('./pewpew.txt', function (err, data) {
if (err) { return void console.error(err); }
console.log(data);
});
read('./pewpew.txt', function (err, data) {
if (err) { return void console.error(err); }
console.log(data);
});
```
*/
module.exports = function (/* task */) {
var map = {};
return function (id, cb, impl) {
if (typeof(cb) !== 'function' || typeof(impl) !== 'function') {
throw new Error("expected callback and implementation");
}
if (map[id]) { return void map[id].push(cb); }
map[id] = [cb];
impl(function () {
var args = Array.prototype.slice.call(arguments);
//if (map[id] && map[id].length > 1) { console.log("BATCH-READ DID ITS JOB for [%s][%s]", task, id); }
map[id].forEach(function (h) {
h.apply(null, args);
});
delete map[id];
});
};
};

@ -3,19 +3,9 @@ var WebSocket = require("ws"); // jshint ignore:line
var nThen = require("nthen");
var Util = require("../../www/common/common-util");
var Rpc = require("../../www/common/rpc");
var Nacl = require("tweetnacl");
var makeKeys = function () {
var keys = Nacl.sign.keyPair.fromSeed(Nacl.randomBytes(Nacl.sign.seedLength));
return {
secret: Nacl.util.encodeBase64(keys.secretKey),
public: Nacl.util.encodeBase64(keys.publicKey),
};
};
var Client = module.exports;
var createNetwork = Client.createNetwork = function (url, cb) {
@ -24,13 +14,14 @@ var createNetwork = Client.createNetwork = function (url, cb) {
var info = {};
Netflux.connect(url, function (url) {
// this websocket seems to never close properly if the error is
// ECONNREFUSED
info.websocket = new WebSocket(url)
.on('error', function (err) {
console.log(err);
CB(err);
})
.on('close', function (err) {
console.log("close");
console.log(err);
.on('close', function (/* err */) {
delete info.websocket;
});
return info.websocket;
}).then(function (network) {
@ -77,7 +68,10 @@ Client.create = function (config, cb) {
if (config.network) { return; }
// connect to the network...
createNetwork('ws://localhost:3000/cryptpad_websocket', w(function (err, info) {
//console.log(_network);
if (err) {
w.abort();
return void CB(err);
}
config.network = info.network;
config.websocket = info.websocket;
}));
@ -97,21 +91,6 @@ Client.create = function (config, cb) {
w.abort();
CB(err);
});
}).nThen(function (w) {
// connect to the anonRpc
Rpc.createAnonymous(config.network, w(function (err, rpc) {
if (err) {
return void CB('ANON_RPC_CONNECT_ERR');
}
client.anonRpc = rpc;
}));
var keys = makeKeys();
Rpc.create(config.network, keys.secret, keys.public, w(function (err, rpc) {
if (err) {
return void CB('RPC_CONNECT_ERR');
}
client.rpc = rpc;
}));
}).nThen(function () {
CB(void 0, client);
});

@ -195,6 +195,10 @@ Meta.createLineHandler = function (ref, errorHandler) {
return function (err, line) {
if (err) {
// it's not abnormal that metadata exists without a corresponding log
// so ENOENT is fine
if (ref.index === 0 && err.code === 'ENOENT') { return; }
// any other errors are abnormal
return void errorHandler('METADATA_HANDLER_LINE_ERR', {
error: err,
index: ref.index,

20
package-lock.json generated

@ -98,10 +98,24 @@
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
},
"chainpad-crypto": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/chainpad-crypto/-/chainpad-crypto-0.2.2.tgz",
"integrity": "sha512-7MJ7qPz/C4sJPsDhPMjdSRmliOCPoRO0XM1vUomcgXA6HINlW+if9AAt/H4q154nYhZ/b57njgC6cWgd/RDidg==",
"requires": {
"tweetnacl": "git://github.com/dchest/tweetnacl-js.git#v0.12.2"
},
"dependencies": {
"tweetnacl": {
"version": "git://github.com/dchest/tweetnacl-js.git#8a21381d696acdc4e99c9f706f1ad23285795f79",
"from": "git://github.com/dchest/tweetnacl-js.git#v0.12.2"
}
}
},
"chainpad-server": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/chainpad-server/-/chainpad-server-3.0.3.tgz",
"integrity": "sha512-NRfV7FFBEYy4ZVX7h0P5znu55X8v5K4iGWeMGihkfWZLKu70GmCPUTwpBCP79dUvnCToKEa4/e8aoSPcvZC8pA==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/chainpad-server/-/chainpad-server-3.0.5.tgz",
"integrity": "sha512-USKOMSHsNjnme81Qy3nQ+ji9eCkBPokYH4T82LVHAI0aayTSCXcTPUDLVGDBCRqe8NsXU4io1WPXn1KiZwB8fA==",
"requires": {
"nthen": "^0.1.8",
"pull-stream": "^3.6.9",

@ -8,7 +8,8 @@
"url": "git://github.com/xwiki-labs/cryptpad.git"
},
"dependencies": {
"chainpad-server": "~3.0.2",
"chainpad-crypto": "^0.2.2",
"chainpad-server": "^3.0.5",
"express": "~4.16.0",
"fs-extra": "^7.0.0",
"get-folder-size": "^2.0.1",
@ -39,7 +40,7 @@
"lint:less": "./node_modules/lesshint/bin/lesshint -c ./.lesshintrc ./customize.dist/src/less2/",
"flow": "./node_modules/.bin/flow",
"test": "node scripts/TestSelenium.js",
"test-rpc": "cd scripts && node test-rpc",
"test-rpc": "cd scripts/tests && node test-rpc",
"template": "cd customize.dist/src && for page in ../index.html ../privacy.html ../terms.html ../about.html ../contact.html ../what-is-cryptpad.html ../features.html ../../www/login/index.html ../../www/register/index.html ../../www/user/index.html;do echo $page; cp template.html $page; done;"
}
}

@ -19,6 +19,7 @@ const getFolderSize = require("get-folder-size");
const Pins = require("./lib/pins");
const Meta = require("./lib/metadata");
const WriteQueue = require("./lib/write-queue");
const BatchRead = require("./lib/batch-read");
var RPC = module.exports;
@ -231,6 +232,7 @@ var checkSignature = function (signedMsg, signature, publicKey) {
return Nacl.sign.detached.verify(signedBuffer, signatureBuffer, pubBuffer);
};
const batchUserPins = BatchRead("LOAD_USER_PINS");
var loadUserPins = function (Env, publicKey, cb) {
var session = getSession(Env.Sessions, publicKey);
@ -238,6 +240,7 @@ var loadUserPins = function (Env, publicKey, cb) {
return cb(session.channels);
}
batchUserPins(publicKey, cb, function (done) {
var ref = {};
var lineHandler = Pins.createLineHandler(ref, function (label, data) {
Log.error(label, {
@ -252,7 +255,8 @@ var loadUserPins = function (Env, publicKey, cb) {
// only put this into the cache if it completes
session.channels = ref.pins;
cb(ref.pins);
done(ref.pins); // FIXME no error handling?
});
});
};
@ -268,12 +272,12 @@ var getChannelList = function (Env, publicKey, cb) {
});
};
var makeFilePath = function (root, id) {
var makeFilePath = function (root, id) { // FIXME FILES
if (typeof(id) !== 'string' || id.length <= 2) { return null; }
return Path.join(root, id.slice(0, 2), id);
};
var getUploadSize = function (Env, channel, cb) {
var getUploadSize = function (Env, channel, cb) { // FIXME FILES
var paths = Env.paths;
var path = makeFilePath(paths.blob, channel);
if (!path) {
@ -290,45 +294,49 @@ var getUploadSize = function (Env, channel, cb) {
});
};
const batchFileSize = BatchRead("GET_FILE_SIZE");
var getFileSize = function (Env, channel, cb) {
if (!isValidId(channel)) { return void cb('INVALID_CHAN'); }
batchFileSize(channel, cb, function (done) {
if (channel.length === 32) {
if (typeof(Env.msgStore.getChannelSize) !== 'function') {
return cb('GET_CHANNEL_SIZE_UNSUPPORTED');
return done('GET_CHANNEL_SIZE_UNSUPPORTED');
}
return void Env.msgStore.getChannelSize(channel, function (e, size /*:number*/) {
if (e) {
if (e.code === 'ENOENT') { return void cb(void 0, 0); }
return void cb(e.code);
if (e.code === 'ENOENT') { return void done(void 0, 0); }
return void done(e.code);
}
cb(void 0, size);
done(void 0, size);
});
}
// 'channel' refers to a file, so you need another API
getUploadSize(Env, channel, function (e, size) {
if (typeof(size) === 'undefined') { return void cb(e); }
cb(void 0, size);
if (typeof(size) === 'undefined') { return void done(e); }
done(void 0, size);
});
});
};
const batchMetadata = BatchRead("GET_METADATA");
var getMetadata = function (Env, channel, cb) {
if (!isValidId(channel)) { return void cb('INVALID_CHAN'); }
if (channel.length !== 32) { return cb("INVALID_CHAN"); }
batchMetadata(channel, cb, function (done) {
var ref = {};
var lineHandler = Meta.createLineHandler(ref, Log.error);
return void Env.msgStore.readChannelMetadata(channel, lineHandler, function (err) {
if (err) {
// stream errors?
return void cb(err);
return void done(err);
}
cb(void 0, ref.meta);
done(void 0, ref.meta);
});
});
};
@ -470,19 +478,22 @@ var getDeletedPads = function (Env, channels, cb) {
});
};
const batchTotalSize = BatchRead("GET_TOTAL_SIZE");
var getTotalSize = function (Env, publicKey, cb) {
batchTotalSize(publicKey, cb, function (done) {
var bytes = 0;
return void getChannelList(Env, publicKey, function (channels) {
if (!channels) { return cb('INVALID_PIN_LIST'); } // unexpected
if (!channels) { return done('INVALID_PIN_LIST'); } // unexpected
var count = channels.length;
if (!count) { cb(void 0, 0); }
if (!count) { return void done(void 0, 0); }
channels.forEach(function (channel) {
channels.forEach(function (channel) { // FIXME this might as well be nThen
getFileSize(Env, channel, function (e, size) {
count--;
if (!e) { bytes += size; }
if (count === 0) { return cb(void 0, bytes); }
if (count === 0) { return done(void 0, bytes); }
});
});
});
});
@ -538,7 +549,7 @@ var applyCustomLimits = function (Env, config) {
// The limits object contains storage limits for all the publicKey that have paid
// To each key is associated an object containing the 'limit' value and a 'note' explaining that limit
var updateLimits = function (Env, config, publicKey, cb /*:(?string, ?any[])=>void*/) {
var updateLimits = function (Env, config, publicKey, cb /*:(?string, ?any[])=>void*/) { // FIXME BATCH?
if (config.adminEmail === false) {
applyCustomLimits(Env, config);
@ -831,7 +842,7 @@ var resetUserPins = function (Env, publicKey, channelList, cb) {
});
};
var makeFileStream = function (root, id, cb) {
var makeFileStream = function (root, id, cb) { // FIXME FILES
var stub = id.slice(0, 2);
var full = makeFilePath(root, id);
if (!full) {
@ -862,7 +873,7 @@ var makeFileStream = function (root, id, cb) {
});
};
var isFile = function (filePath, cb) {
var isFile = function (filePath, cb) { // FIXME FILES
/*:: if (typeof(filePath) !== 'string') { throw new Error('should never happen'); } */
Fs.stat(filePath, function (e, stats) {
if (e) {
@ -892,8 +903,7 @@ var clearOwnedChannel = function (Env, channelId, unsafeKey, cb) {
});
};
var removeOwnedBlob = function (Env, blobId, unsafeKey, cb) {
// FIXME METADATA
var removeOwnedBlob = function (Env, blobId, unsafeKey, cb) { // FIXME FILES // FIXME METADATA
var safeKey = escapeKeyCharacters(unsafeKey);
var safeKeyPrefix = safeKey.slice(0,3);
var blobPrefix = blobId.slice(0,2);
@ -1009,7 +1019,7 @@ var removePins = function (Env, safeKey, cb) {
});
};
var upload = function (Env, publicKey, content, cb) {
var upload = function (Env, publicKey, content, cb) { // FIXME FILES
var paths = Env.paths;
var dec;
try { dec = Buffer.from(content, 'base64'); }
@ -1045,7 +1055,7 @@ var upload = function (Env, publicKey, content, cb) {
}
};
var upload_cancel = function (Env, publicKey, fileSize, cb) {
var upload_cancel = function (Env, publicKey, fileSize, cb) { // FIXME FILES
var paths = Env.paths;
var session = getSession(Env.Sessions, publicKey);
@ -1069,7 +1079,7 @@ var upload_cancel = function (Env, publicKey, fileSize, cb) {
});
};
var upload_complete = function (Env, publicKey, id, cb) { // FIXME logging
var upload_complete = function (Env, publicKey, id, cb) { // FIXME FILES
var paths = Env.paths;
var session = getSession(Env.Sessions, publicKey);
@ -1085,7 +1095,7 @@ var upload_complete = function (Env, publicKey, id, cb) { // FIXME logging
var oldPath = makeFilePath(paths.staging, publicKey);
if (!oldPath) {
WARN('safeMkdir', "oldPath is null"); // FIXME logging
WARN('safeMkdir', "oldPath is null");
return void cb('RENAME_ERR');
}
@ -1093,13 +1103,13 @@ var upload_complete = function (Env, publicKey, id, cb) { // FIXME logging
var prefix = id.slice(0, 2);
var newPath = makeFilePath(paths.blob, id);
if (typeof(newPath) !== 'string') {
WARN('safeMkdir', "newPath is null"); // FIXME logging
WARN('safeMkdir', "newPath is null");
return void cb('RENAME_ERR');
}
Fse.mkdirp(Path.join(paths.blob, prefix), function (e) {
if (e || !newPath) {
WARN('safeMkdir', e); // FIXME logging
WARN('safeMkdir', e);
return void cb('RENAME_ERR');
}
isFile(newPath, function (e, yes) {
@ -1122,7 +1132,6 @@ var upload_complete = function (Env, publicKey, id, cb) { // FIXME logging
return void cb(e || 'PATH_ERR');
}
// lol wut handle ur errors
Fse.move(oldPath, newPath, function (e) {
if (e) {
WARN('rename', e);
@ -1135,7 +1144,7 @@ var upload_complete = function (Env, publicKey, id, cb) { // FIXME logging
tryLocation(handleMove);
};
/*
/* FIXME FILES
var owned_upload_complete = function (Env, safeKey, cb) {
var session = getSession(Env.Sessions, safeKey);
@ -1230,7 +1239,7 @@ var owned_upload_complete = function (Env, safeKey, cb) {
};
*/
var owned_upload_complete = function (Env, safeKey, id, cb) { // FIXME logging
var owned_upload_complete = function (Env, safeKey, id, cb) { // FIXME FILES
var session = getSession(Env.Sessions, safeKey);
// the file has already been uploaded to the staging area
@ -1341,7 +1350,7 @@ var owned_upload_complete = function (Env, safeKey, id, cb) { // FIXME logging
});
};
var upload_status = function (Env, publicKey, filesize, cb) {
var upload_status = function (Env, publicKey, filesize, cb) { // FIXME FILES
var paths = Env.paths;
// validate that the provided size is actually a positive number
@ -1387,7 +1396,7 @@ var upload_status = function (Env, publicKey, filesize, cb) {
author of the block, since we assume that the block will have been
encrypted with xsalsa20-poly1305 which is authenticated.
*/
var validateLoginBlock = function (Env, publicKey, signature, block, cb) {
var validateLoginBlock = function (Env, publicKey, signature, block, cb) { // FIXME BLOCKS
// convert the public key to a Uint8Array and validate it
if (typeof(publicKey) !== 'string') { return void cb('E_INVALID_KEY'); }
@ -1428,7 +1437,7 @@ var validateLoginBlock = function (Env, publicKey, signature, block, cb) {
return void cb(null, u8_block);
};
var createLoginBlockPath = function (Env, publicKey) {
var createLoginBlockPath = function (Env, publicKey) { // FIXME BLOCKS
// prepare publicKey to be used as a file name
var safeKey = escapeKeyCharacters(publicKey);
@ -1442,7 +1451,7 @@ var createLoginBlockPath = function (Env, publicKey) {
return Path.join(Env.paths.block, safeKey.slice(0, 2), safeKey);
};
var writeLoginBlock = function (Env, msg, cb) {
var writeLoginBlock = function (Env, msg, cb) { // FIXME BLOCKS
//console.log(msg);
var publicKey = msg[0];
var signature = msg[1];
@ -1473,7 +1482,7 @@ var writeLoginBlock = function (Env, msg, cb) {
cb(e);
}
}));
}).nThen(function () { // FIXME logging
}).nThen(function () {
// actually write the block
// flow is dumb and I need to guard against this which will never happen
@ -1497,7 +1506,7 @@ var writeLoginBlock = function (Env, msg, cb) {
information, we can just sign some constant and use that as proof.
*/
var removeLoginBlock = function (Env, msg, cb) {
var removeLoginBlock = function (Env, msg, cb) { // FIXME BLOCKS
var publicKey = msg[0];
var signature = msg[1];
var block = Nacl.util.decodeUTF8('DELETE_BLOCK'); // clients and the server will have to agree on this constant
@ -1605,7 +1614,9 @@ var writePrivateMessage = function (Env, args, nfwssCtx, cb) {
});
};
const batchDiskUsage = BatchRead("GET_DISK_USAGE");
var getDiskUsage = function (Env, cb) {
batchDiskUsage('', cb, function (done) {
var data = {};
nThen(function (waitFor) {
getFolderSize('./', waitFor(function(err, info) {
@ -1627,10 +1638,14 @@ var getDiskUsage = function (Env, cb) {
data.datastore = info;
}));
}).nThen(function () {
cb (void 0, data);
done(void 0, data);
});
});
};
const batchRegisteredUsers = BatchRead("GET_REGISTERED_USERS");
var getRegisteredUsers = function (Env, cb) {
batchRegisteredUsers('', cb, function (done) {
var dir = Env.paths.pin;
var folders;
var users = 0;
@ -1638,7 +1653,7 @@ var getRegisteredUsers = function (Env, cb) {
Fs.readdir(dir, waitFor(function (err, list) {
if (err) {
waitFor.abort();
return void cb(err);
return void done(err);
}
folders = list;
}));
@ -1651,7 +1666,8 @@ var getRegisteredUsers = function (Env, cb) {
}));
});
}).nThen(function () {
cb(void 0, users);
done(void 0, users);
});
});
};
var getActiveSessions = function (Env, ctx, cb) {
@ -1821,6 +1837,7 @@ RPC.create = function (
};
var handleUnauthenticatedMessage = function (msg, respond, nfwssCtx) {
Log.silly('LOG_RPC', msg[0]);
switch (msg[0]) {
case 'GET_HISTORY_OFFSET': {
if (typeof(msg[1]) !== 'object' || typeof(msg[1].channelName) !== 'string') {
@ -1844,7 +1861,7 @@ RPC.create = function (
respond(e, [null, size, null]);
});
case 'GET_METADATA':
return void getMetadata(Env, msg[1], function (e, data) { // FIXME METADATA
return void getMetadata(Env, msg[1], function (e, data) {
WARN(e, msg[1]);
respond(e, [null, data, null]);
});

@ -1,43 +0,0 @@
/* globals process */
var Client = require("../lib/client/");
var Mailbox = require("../www/bower_components/chainpad-crypto").Mailbox;
var Nacl = require("tweetnacl");
var makeKeys = function () {
var pair = Nacl.box.keyPair();
return {
curvePrivate: Nacl.util.encodeBase64(pair.secretKey),
curvePublic: Nacl.util.encodeBase64(pair.publicKey),
};
};
Client.create(function (err, client) {
if (err) {
console.error(err);
process.exit(1);
}
var channel = "d34ebe83931382fcad9fe2e2d0e2cb5f"; // channel
var recipient = "e8jvf36S3chzkkcaMrLSW7PPrz7VDp85lIFNI26dTmw="; // curvePublic
var keys = makeKeys();
var cryptor = Mailbox.createEncryptor(keys);
var message = cryptor.encrypt(JSON.stringify({
type: "CHEESE",
author: keys.curvePublic,
content: {
text: "CAMEMBERT",
}
}), recipient);
client.anonRpc.send('WRITE_PRIVATE_MESSAGE', [channel, message], function (err, response) {
if (err) {
return void console.error(err);
}
response = response;
// shutdown doesn't work, so we need to do this instead
client.shutdown();
});
});

@ -0,0 +1 @@
require("./test-rpc");

@ -0,0 +1,295 @@
/* globals process */
var Client = require("../../lib/client/");
var Mailbox = require("../../www/bower_components/chainpad-crypto").Mailbox;
var Nacl = require("tweetnacl");
var nThen = require("nthen");
var Rpc = require("../../www/common/rpc");
var Hash = require("../../www/common/common-hash");
var CpNetflux = require("../../www/bower_components/chainpad-netflux");
var createMailbox = function (config, cb) {
var webchannel;
CpNetflux.start({
network: config.network,
channel: config.channel,
crypto: config.crypto,
owners: [ config.edPublic ],
noChainPad: true,
onConnect: function (wc /*, sendMessage */) {
webchannel = wc;
},
onMessage: function (/* msg, user, vKey, isCp, hash, author */) {
},
onReady: function () {
cb(void 0, webchannel);
},
});
};
process.on('unhandledRejection', function (err) {
console.error(err);
});
var makeCurveKeys = function () {
var pair = Nacl.box.keyPair();
return {
curvePrivate: Nacl.util.encodeBase64(pair.secretKey),
curvePublic: Nacl.util.encodeBase64(pair.publicKey),
};
};
var makeEdKeys = function () {
var keys = Nacl.sign.keyPair.fromSeed(Nacl.randomBytes(Nacl.sign.seedLength));
return {
edPrivate: Nacl.util.encodeBase64(keys.secretKey),
edPublic: Nacl.util.encodeBase64(keys.publicKey),
};
};
var EMPTY_ARRAY_HASH = 'slspTLTetp6gCkw88xE5BIAbYBXllWvQGahXCx/h1gQOlE7zze4W0KRlA8puZZol8hz5zt3BPzUqPJgTjBXWrw==';
var createUser = function (config, cb) {
// config should contain keys for a team rpc (ed)
// teamEdKeys
var user;
nThen(function (w) {
Client.create(w(function (err, client) {
if (err) {
w.abort();
return void cb(err);
}
user = client;
}));
}).nThen(function (w) {
// make all the parameters you'll need
var network = user.network = user.config.network;
user.edKeys = makeEdKeys();
user.curveKeys = makeCurveKeys();
user.mailbox = Mailbox.createEncryptor(user.curveKeys);
user.mailboxChannel = Hash.createChannelId();
// create an anon rpc for alice
Rpc.createAnonymous(network, w(function (err, rpc) {
if (err) {
w.abort();
user.shutdown();
return void console.error('ANON_RPC_CONNECT_ERR');
}
user.anonRpc = rpc;
}));
Rpc.create(network, user.edKeys.edPrivate, user.edKeys.edPublic, w(function (err, rpc) {
if (err) {
w.abort();
user.shutdown();
console.error(err);
return console.log('RPC_CONNECT_ERR');
}
user.rpc = rpc;
}));
Rpc.create(network, config.teamEdKeys.edPrivate, config.teamEdKeys.edPublic, w(function (err, rpc) {
if (err) {
w.abort();
user.shutdown();
return console.log('RPC_CONNECT_ERR');
}
user.team_rpc = rpc;
}));
}).nThen(function (w) {
// some basic sanity checks...
user.rpc.send('GET_HASH', user.edKeys.edPublic, w(function (err, hash) {
if (err) {
w.abort();
return void cb(err);
}
if (!hash || hash[0] !== EMPTY_ARRAY_HASH) {
console.error("EXPECTED EMPTY ARRAY HASH");
process.exit(1);
}
}));
}).nThen(function (w) {
// create and subscribe to your mailbox
createMailbox({
network: user.network,
channel: user.mailboxChannel,
crypto: user.mailbox,
edPublic: user.edKeys.edPublic,
}, w(function (err, wc) {
if (err) {
w.abort();
console.error("Mailbox creation error");
process.exit(1);
}
wc.leave();
}));
}).nThen(function (w) {
// confirm that you own your mailbox
user.anonRpc.send("GET_METADATA", user.mailboxChannel, w(function (err, data) {
if (err) {
w.abort();
return void cb(err);
}
try {
if (data[0].owners[0] !== user.edKeys.edPublic) {
throw new Error("INCORRECT MAILBOX OWNERSHIP METADATA");
}
} catch (err2) {
w.abort();
return void cb(err2);
}
}));
}).nThen(function (w) {
// pin your mailbox
user.rpc.send('PIN', [user.mailboxChannel], w(function (err, data) {
if (err) {
w.abort();
return void cb(err);
}
try {
if (data[0] === EMPTY_ARRAY_HASH) { throw new Error("PIN_DIDNT_WORK"); }
user.latestPinHash = data[0];
} catch (err2) {
w.abort();
return void cb(err2);
}
}));
}).nThen(function (w) {
user.team_rpc.send('GET_HASH', config.teamEdKeys.edPublic, w(function (err, hash) {
if (err) {
w.abort();
return void cb(err);
}
if (!hash || hash[0] !== EMPTY_ARRAY_HASH) {
console.error("EXPECTED EMPTY ARRAY HASH");
process.exit(1);
}
}));
}).nThen(function () {
// TODO check your quota usage
}).nThen(function (w) {
user.rpc.send('UNPIN', [user.mailboxChannel], w(function (err, data) {
if (err) {
w.abort();
return void cb(err);
}
try {
if (data[0] !== EMPTY_ARRAY_HASH) { throw new Error("UNPIN_DIDNT_WORK"); }
user.latestPinHash = data[0];
} catch (err2) {
w.abort();
return void cb(err2);
}
}));
}).nThen(function (w) {
// clean up the pin list to avoid lots of accounts on the server
user.rpc.send("REMOVE_PINS", undefined, w(function (err, data) {
if (err) {
w.abort();
return void cb(err);
}
if (!data || data[0] !== 'OK') {
w.abort();
return void cb("REMOVE_PINS_DIDNT_WORK");
}
}));
}).nThen(function () {
user.cleanup = function (cb) {
// TODO remove your mailbox
cb = cb;
};
cb(void 0, user);
});
};
var alice, bob;
nThen(function (w) {
var sharedConfig = {
teamEdKeys: makeEdKeys(),
};
createUser(sharedConfig, w(function (err, _alice) {
if (err) {
w.abort();
return void console.log(err);
}
alice = _alice;
}));
createUser(sharedConfig, w(function (err, _bob) {
if (err) {
w.abort();
return void console.log(err);
}
bob = _bob;
}));
}).nThen(function (w) {
var message = alice.mailbox.encrypt(JSON.stringify({
type: "CHEESE",
author: alice.curveKeys.curvePublic,
content: {
text: "CAMEMBERT",
}
}), bob.curveKeys.curvePublic);
alice.anonRpc.send('WRITE_PRIVATE_MESSAGE', [bob.mailboxChannel, message], w(function (err, response) {
if (err) {
return void console.error(err);
}
// XXX validate that the write was actually successful by checking its size
response = response;
// shutdown doesn't work, so we need to do this instead
}));
}).nThen(function () {
nThen(function () {
}).nThen(function () {
// make a drive
// pin it
}).nThen(function () { // MAILBOXES
// write to your mailbox
// pin your mailbox
}).nThen(function () {
// create an owned pad
// pin the pad
// write to it
}).nThen(function () {
// get pinned usage
// remember the usage
}).nThen(function () {
// upload a file
// remember its size
}).nThen(function () {
// get pinned usage
// check that it is consistent with the size of your uploaded file
}).nThen(function () {
// delete your uploaded file
// unpin your owned file
}).nThen(function () { // EDITABLE METADATA
//
}).nThen(function () {
});
}).nThen(function () {
alice.shutdown();
bob.shutdown();
});

@ -1,11 +1,5 @@
define([
'/common/common-util.js',
'/customize/messages.js',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/tweetnacl/nacl-fast.min.js'
], function (Util, Messages, Crypto) {
var Nacl = window.nacl;
(function (window) {
var factory = function (Util, Crypto, Nacl) {
var Hash = window.CryptPad_Hash = {};
var uint8ArrayToHex = Util.uint8ArrayToHex;
@ -510,20 +504,6 @@ Version 1
'/' + curvePublic.replace(/\//g, '-') + '/';
};
// Create untitled documents when no name is given
var getLocaleDate = function () {
if (window.Intl && window.Intl.DateTimeFormat) {
var options = {weekday: "short", year: "numeric", month: "long", day: "numeric"};
return new window.Intl.DateTimeFormat(undefined, options).format(new Date());
}
return new Date().toString().split(' ').slice(0,4).join(' ');
};
Hash.getDefaultName = function (parsed) {
var type = parsed.type;
var name = (Messages.type)[type] + ' - ' + getLocaleDate();
return name;
};
Hash.isValidHref = function (href) {
// Non-empty href?
if (!href) { return; }
@ -547,4 +527,19 @@ Version 1
};
return Hash;
});
};
if (typeof(module) !== 'undefined' && module.exports) {
module.exports = factory(require("./common-util"), require("chainpad-crypto"), require("tweetnacl"));
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
define([
'/common/common-util.js',
'/bower_components/chainpad-crypto/crypto.js',
'/bower_components/tweetnacl/nacl-fast.min.js'
], function (Util, Crypto) {
return factory(Util, Crypto, window.nacl);
});
} else {
// unsupported initialization
}
}(typeof(window) !== 'undefined'? window : {}));

@ -124,6 +124,7 @@ define([
f = f || user;
if (f.name) {
f.displayName = f.name;
f.edPublic = edPublic;
}
}
_owners[ed] = f || {

@ -1,5 +1,10 @@
(function (window) {
var Util = {};
Util.tryParse = function (s) {
try { return JSON.parse(s); } catch (e) { return;}
};
Util.mkAsync = function (f) {
return function () {
var args = Array.prototype.slice.call(arguments);

@ -659,7 +659,6 @@ define([
data.href = parsed.getUrl({present: parsed.present});
if (typeof (data.title) !== "string") { return cb('Missing title'); }
if (data.title.trim() === "") { data.title = Hash.getDefaultName(parsed); }
if (common.initialPath) {
if (!data.path) {

@ -563,7 +563,7 @@ define([
roHref: roHref,
atime: now,
ctime: now,
title: title || Hash.getDefaultName(Hash.parsePadUrl(href)),
title: title || UserObject.getDefaultName(Hash.parsePadUrl(href)),
};
};
@ -933,6 +933,8 @@ define([
var p = Hash.parsePadUrl(href);
var h = p.hashData;
if (title.trim() === "") { title = UserObject.getDefaultName(p); }
if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); }
if (p.type === "debug") { return void cb(); }

@ -189,7 +189,6 @@ proxy.mailboxes = {
box.queue.push(msg);
}
};
Crypto = Crypto;
if (!Crypto.Mailbox) {
return void console.error("chainpad-crypto is outdated and doesn't support mailboxes.");
}

@ -695,7 +695,7 @@ define([
// Fix creation time
if (!el.ctime) { el.ctime = el.atime; }
// Fix title
if (!el.title) { el.title = Hash.getDefaultName(parsed); }
if (!el.title) { el.title = exp.getDefaultName(parsed); }
// Fix channel
if (!el.channel) {
try {

@ -1,73 +1,105 @@
(function () {
var factory = function (Util, Nacl) {
// we will send messages with a unique id for each RPC
// that id is returned with each response, indicating which call it was in response to
var uid = Util.uid;
// safely parse json messages, because they might cause parse errors
var tryParse = Util.tryParse;
// we will sign various message with our edPrivate keys
// this handles that in a generic way
var signMsg = function (data, signKey) {
var buffer = Nacl.util.decodeUTF8(JSON.stringify(data));
return Nacl.util.encodeBase64(Nacl.sign.detached(buffer, signKey));
};
/*
types of messages:
pin -> hash
unpin -> hash
getHash -> hash
getTotalSize -> bytes
getFileSize -> bytes
*/
// sendMsg takes a pre-formed message, does a little validation
// adds a transaction id to the message and stores its callback
// and finally sends it off to the historyKeeper, which delegates its
// processing to the RPC submodule
var sendMsg = function (ctx, data, cb) {
if (typeof(cb) !== 'function') { throw new Error('expected callback'); }
var network = ctx.network;
var hkn = network.historyKeeper;
var txid = uid();
if (typeof(hkn) !== 'string') { return void cb("NO_HISTORY_KEEPER"); }
if (typeof(cb) !== 'function') {
return console.error('expected callback');
}
var txid = uid();
var pending = ctx.pending[txid] = function (err, response) {
cb(err, response);
};
pending.data = data;
pending.called = 0;
return network.sendto(hkn, JSON.stringify([txid, data]));
};
var parse = function (msg) {
try {
return JSON.parse(msg);
} catch (e) {
return null;
}
var matchesAnon = function (ctx, txid) {
if (!ctx.anon) { return false; }
if (typeof(ctx.anon.pending[txid]) !== 'function') { return false; }
return true;
};
var onMsg = function (ctx, msg) {
var parsed = parse(msg);
var handleAnon = function (ctx /* anon_ctx */, txid, body /* parsed messages without txid */) {
// if anon is handling it we know there's a pending callback
var pending = ctx.pending[txid];
if (body[0] === 'ERROR') { pending(body[1]); }
else { pending(void 0, body.slice(1)); }
delete ctx.pending[txid];
};
var onMsg = function (ctx /* network context */, msg /* string message */) {
if (typeof(msg) !== 'string') {
console.error("received non-string message [%s]", msg);
}
var parsed = tryParse(msg);
if (!parsed) {
return void console.error(new Error('could not parse message: %s', msg));
}
// RPC messages are always arrays.
if (!Array.isArray(parsed)) { return; }
// ignore FULL_HISTORY messages
if (/(FULL_HISTORY|HISTORY_RANGE)/.test(parsed[0])) { return; }
var txid = parsed[0];
// txid must be a string, or this message is not meant for us
if (typeof(txid) !== 'string') { return; }
var cookie = parsed[1];
var pending = ctx.pending[txid];
if (matchesAnon(ctx, txid)) {
return void handleAnon(ctx.anon, txid, parsed.slice(1));
}
if (!(parsed && parsed.slice)) {
// RPC responses are arrays. this message isn't meant for us.
return;
// iterate over authenticated rpc contexts and check if they are expecting
// a message with this txid
if (ctx.authenticated.some(function (rpc_ctx) {
var pending = rpc_ctx.pending[txid];
// not meant for you
if (typeof(pending) !== 'function') { return false; }
// if you're here, the message is for you...
if (parsed[1] !== 'ERROR') {
// if the server sent you a new cookie, replace the old one
if (/\|/.test(parsed[1]) && rpc_ctx.cookie !== parsed[1]) {
rpc_ctx.cookie = parsed[1];
}
if (/(FULL_HISTORY|HISTORY_RANGE)/.test(parsed[0])) { return; }
pending(void 0, parsed.slice(2));
var response = parsed.slice(2);
// if successful, delete the callback...
delete rpc_ctx.pending[txid];
// prevent further iteration
return true;
}
if (typeof(pending) === 'function') {
if (parsed[1] === 'ERROR') {
// NO_COOKIE errors mean you failed to authenticate.
// request a new cookie and resend the query
if (parsed[2] === 'NO_COOKIE') {
return void ctx.send('COOKIE', "", function (e) {
rpc_ctx.send('COOKIE', "", function (e) {
if (e) {
console.error(e);
return void pending(e);
@ -75,70 +107,91 @@ types of messages:
// resend the same command again
// give up if you've already tried resending
if (ctx.resend(txid)) { delete ctx.pending[txid]; }
if (rpc_ctx.resend(txid)) { delete rpc_ctx.pending[txid]; }
});
// prevent further iteration
return true;
}
// if you're here then your RPC passed authentication but had some other error
// call back with the error message
pending(parsed[2]);
delete ctx.pending[txid];
return;
} else {
// update the cookie
if (/\|/.test(cookie)) {
if (ctx.cookie !== cookie) {
ctx.cookie = cookie;
}
}
}
pending(void 0, response);
// and delete the pending callback
delete rpc_ctx.pending[txid];
// if successful, delete the callback...
delete ctx.pending[txid];
// prevent further iteration
return true;
})) {
// the message was handled, so stop here
return;
}
// HACK to hide messages from the anon rpc
if (parsed.length !== 4 && parsed[1] !== 'ERROR') {
console.log(parsed);
console.error("received message [%s] for txid[%s] with no callback", msg, txid);
}
console.error("UNHANDLED RPC MESSAGE", msg);
};
var create = function (network, edPrivateKey, edPublicKey, cb) {
var signKey;
var networks = [];
var contexts = [];
try {
signKey = Nacl.util.decodeBase64(edPrivateKey);
if (signKey.length !== 64) {
throw new Error('private key did not match expected length of 64');
}
} catch (err) {
return void cb(err);
}
var initNetworkContext = function (network) {
var ctx = {
network: network,
connected: true,
anon: undefined,
authenticated: [],
};
networks.push(network);
contexts.push(ctx);
var pubBuffer;
try {
pubBuffer = Nacl.util.decodeBase64(edPublicKey);
if (pubBuffer.length !== 32) {
return void cb('expected public key to be 32 uint');
}
} catch (err) {
return void cb(err);
}
// add listeners...
network.on('message', function (msg, sender) {
if (sender !== network.historyKeeper) { return; }
onMsg(ctx, msg);
});
network.on('disconnect', function () {
ctx.connected = false;
if (ctx.anon) { ctx.anon.connected = false; }
ctx.authenticated.forEach(function (ctx) {
ctx.connected = false;
});
});
network.on('reconnect', function () {
if (ctx.anon) { ctx.anon.connected = true; }
ctx.authenticated.forEach(function (ctx) {
ctx.connected = true;
});
});
return ctx;
};
var getNetworkContext = function (network) {
var i;
networks.some(function (current, j) {
if (network !== current) { return false; }
i = j;
return true;
});
if (contexts[i]) { return contexts[i]; }
return initNetworkContext(network);
};
var initAuthenticatedRpc = function (networkContext, keys) {
var ctx = {
network: network,
timeouts: {}, // timeouts
pending: {}, // callbacks
network: networkContext.network,
publicKey: keys.publicKeyString,
timeouts: {},
pending: {},
cookie: null,
connected: true,
};
var send = ctx.send = function (type, msg, cb) {
var send = ctx.send = function (type, msg, _cb) {
var cb = Util.mkAsync(_cb);
if (!ctx.connected && type !== 'COOKIE') {
return void setTimeout(function () {
cb('DISCONNECTED');
});
return void cb("DISCONNECTED");
}
// construct a signed message...
@ -150,9 +203,9 @@ types of messages:
data.unshift(ctx.cookie);
}
var sig = signMsg(data, signKey);
var sig = signMsg(data, keys.signKey);
data.unshift(edPublicKey);
data.unshift(keys.publicKeyString);
data.unshift(sig);
// [sig, edPublicKey, cookie, type, msg]
@ -169,25 +222,29 @@ types of messages:
// update the cookie and signature...
pending.data[2] = ctx.cookie;
pending.data[0] = signMsg(pending.data.slice(2), signKey);
pending.data[0] = signMsg(pending.data.slice(2), keys.signKey);
// store the callback with a new txid
var new_txid = uid();
ctx.pending[new_txid] = pending;
// and delete the old one
delete ctx.pending[txid];
try {
return ctx.network.sendto(ctx.network.historyKeeper,
JSON.stringify([txid, pending.data]));
JSON.stringify([new_txid, pending.data]));
} catch (e) {
console.log("failed to resend");
console.error(e);
}
};
send.unauthenticated = function (type, msg, cb) {
if (!ctx.connected) {
return void setTimeout(function () {
cb('DISCONNECTED');
});
}
send.unauthenticated = function (type, msg, _cb) {
var cb = Util.mkAsync(_cb);
if (!ctx.connected) { return void cb('DISCONNECTED'); }
// construct an unsigned message
var data = [null, edPublicKey, null, type, msg];
var data = [null, keys.publicKeyString, null, type, msg];
if (ctx.cookie && ctx.cookie.join) {
data[2] = ctx.cookie.join('|');
} else {
@ -197,103 +254,100 @@ types of messages:
return sendMsg(ctx, data, cb);
};
network.on('message', function (msg, sender) {
if (sender !== network.historyKeeper) { return; }
onMsg(ctx, msg);
ctx.destroy = function () {
// clear all pending timeouts
Object.keys(ctx.timeouts).forEach(function (to) {
clearTimeout(to);
});
network.on('disconnect', function () {
ctx.connected = false;
});
// remove the ctx from the network's stack
var idx = networkContext.authenticated.indexOf(ctx);
if (idx === -1) { return; }
networkContext.authenticated.splice(idx, 1);
};
network.on('reconnect', function () {
send('COOKIE', "", function (e) {
if (e) { return void cb(e); }
ctx.connected = true;
});
});
networkContext.authenticated.push(ctx);
return ctx;
};
// network.onHistoryKeeperChange is defined in chainpad-netflux.js
// The function we pass will be called when the drive reconnects and
// chainpad-netflux detects a new history keeper id
if (network.onHistoryKeeperChange) {
network.onHistoryKeeperChange(function () {
send('COOKIE', "", function (e) {
if (e) { return void cb(e); }
ctx.connected = true;
});
});
}
var getAuthenticatedContext = function (networkContext, keys) {
if (!networkContext) { throw new Error('expected network context'); }
send('COOKIE', "", function (e) {
if (e) { return void cb(e); }
// callback to provide 'send' method to whatever needs it
cb(void 0, { send: send, });
var publicKey = keys.publicKeyString;
var i;
networkContext.authenticated.some(function (ctx, j) {
if (ctx.publicKey !== publicKey) { return false; }
i = j;
return true;
});
};
var onAnonMsg = function (ctx, msg) {
var parsed = parse(msg);
if (networkContext.authenticated[i]) { return networkContext.authenticated[i]; }
if (!parsed) {
return void console.error(new Error('could not parse message: %s', msg));
}
return initAuthenticatedRpc(networkContext, keys);
};
// RPC messages are always arrays.
if (!Array.isArray(parsed)) { return; }
var txid = parsed[0];
var create = function (network, edPrivateKey, edPublicKey, _cb) {
if (typeof(_cb) !== 'function') { throw new Error("expected callback"); }
// txid must be a string, or this message is not meant for us
if (typeof(txid) !== 'string') { return; }
var cb = Util.mkAsync(_cb);
var pending = ctx.pending[txid];
var signKey;
if (!(parsed && parsed.slice)) {
// RPC responses are arrays. this message isn't meant for us.
return;
try {
signKey = Nacl.util.decodeBase64(edPrivateKey);
if (signKey.length !== 64) {
throw new Error('private key did not match expected length of 64');
}
if (/FULL_HISTORY/.test(parsed[0])) { return; }
var response = parsed.slice(2);
if (typeof(pending) === 'function') {
if (parsed[1] === 'ERROR') {
pending(parsed[2]);
delete ctx.pending[txid];
return;
} catch (err) {
return void cb(err);
}
pending(void 0, response);
// if successful, delete the callback...
delete ctx.pending[txid];
return;
}
// HACK: filter out ugly messages we don't care about
if (typeof(msg) !== 'string') {
console.error("received message [%s] for txid[%s] with no callback", msg, txid);
try {
if (Nacl.util.decodeBase64(edPublicKey).length !== 32) {
return void cb('expected public key to be 32 uint');
}
} catch (err) { return void cb(err); }
if (!network) { return void cb('NO_NETWORK'); }
// get or create a context for the provided network
var net_ctx = getNetworkContext(network);
var rpc_ctx = getAuthenticatedContext(net_ctx, {
publicKeyString: edPublicKey,
signKey: signKey,
});
rpc_ctx.send('COOKIE', "", function (e) {
if (e) { return void cb(e); }
// callback to provide 'send' method to whatever needs it
cb(void 0, {
send: rpc_ctx.send,
destroy: rpc_ctx.destroy,
});
});
};
var createAnonymous = function (network, cb) {
var initAnonRpc = function (networkContext) {
var ctx = {
network: network,
timeouts: {}, // timeouts
pending: {}, // callbacks
cookie: null,
network: networkContext.network,
timeouts: {},
pending: {},
connected: true,
};
var send = ctx.send = function (type, msg, cb) {
if (!ctx.connected) {
return void setTimeout(function () {
cb('DISCONNECTED');
});
}
// any particular network will only ever need one anonymous rpc
networkContext.anon = ctx;
ctx.send = function (type, msg, _cb) {
var cb = Util.mkAsync(_cb);
if (!ctx.connected) { return void cb('DISCONNECTED'); }
// construct an unsigned message...
var data = [type, msg];
// [sig, edPublicKey, cookie, type, msg]
// [type, msg]
return sendMsg(ctx, data, cb);
};
@ -314,21 +368,35 @@ types of messages:
}
};
network.on('message', function (msg, sender) {
if (sender !== network.historyKeeper) { return; }
onAnonMsg(ctx, msg);
ctx.destroy = function () {
// clear all pending timeouts
Object.keys(ctx.timeouts).forEach(function (to) {
clearTimeout(to);
});
network.on('disconnect', function () {
ctx.connected = false;
});
networkContext.anon = undefined;
};
network.on('reconnect', function () {
ctx.connected = true;
});
return ctx;
};
var getAnonContext = function (networkContext) {
return networkContext.anon || initAnonRpc(networkContext);
};
var createAnonymous = function (network, _cb) {
// enforce asynchrony
var cb = Util.mkAsync(_cb);
if (typeof(cb) !== 'function') { throw new Error("expected callback"); }
if (!network) { return void cb('NO_NETWORK'); }
// get or create a context for the provided network
var ctx = getAnonContext(getNetworkContext(network));
cb(void 0, {
send: send
send: ctx.send,
destroy: ctx.destroy,
});
};

@ -50,9 +50,10 @@ define([
'/common/outer/local-store.js',
'/customize/application_config.js',
'/common/test.js',
'/common/userObject.js',
], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel,
_FilePicker, _Share, _Messaging, _Notifier, _Hash, _Util, _Realtime,
_Constants, _Feedback, _LocalStore, _AppConfig, _Test) {
_Constants, _Feedback, _LocalStore, _AppConfig, _Test, _UserObject) {
CpNfOuter = _CpNfOuter;
Cryptpad = _Cryptpad;
Crypto = Utils.Crypto = _Crypto;
@ -68,6 +69,7 @@ define([
Utils.Constants = _Constants;
Utils.Feedback = _Feedback;
Utils.LocalStore = _LocalStore;
Utils.UserObject = _UserObject;
AppConfig = _AppConfig;
Test = _Test;
@ -271,7 +273,7 @@ define([
Utils.crypto = Utils.Crypto.createEncryptor(Utils.secret.keys);
var parsed = Utils.Hash.parsePadUrl(window.location.href);
if (!parsed.type) { throw new Error(); }
var defaultTitle = Utils.Hash.getDefaultName(parsed);
var defaultTitle = Utils.UserObject.getDefaultName(parsed);
var edPublic, curvePublic, notifications, isTemplate;
var forceCreationScreen = cfg.useCreationScreen &&
sessionStorage[Utils.Constants.displayPadCreationScreen];
@ -1176,7 +1178,7 @@ define([
// Update metadata values and send new metadata inside
parsed = Utils.Hash.parsePadUrl(window.location.href);
defaultTitle = Utils.Hash.getDefaultName(parsed);
defaultTitle = Utils.UserObject.getDefaultName(parsed);
hashes = Utils.Hash.getHashes(secret);
readOnly = false;
updateMeta();

@ -15,8 +15,24 @@ define([
var TEMPLATE = module.TEMPLATE = "template";
var SHARED_FOLDERS = module.SHARED_FOLDERS = "sharedFolders";
// Create untitled documents when no name is given
var getLocaleDate = function () {
if (window.Intl && window.Intl.DateTimeFormat) {
var options = {weekday: "short", year: "numeric", month: "long", day: "numeric"};
return new window.Intl.DateTimeFormat(undefined, options).format(new Date());
}
return new Date().toString().split(' ').slice(0,4).join(' ');
};
module.getDefaultName = function (parsed) {
var type = parsed.type;
var name = (Messages.type)[type] + ' - ' + getLocaleDate();
return name;
};
module.init = function (files, config) {
var exp = {};
exp.getDefaultName = module.getDefaultName;
var sframeChan = config.sframeChan;
var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Constants.storageKey;

Loading…
Cancel
Save