Merge branch 'staging' into team
This commit is contained in:
@@ -8,6 +8,7 @@ const Crypto = require('crypto');
|
|||||||
const Once = require("./lib/once");
|
const Once = require("./lib/once");
|
||||||
const Meta = require("./lib/metadata");
|
const Meta = require("./lib/metadata");
|
||||||
const WriteQueue = require("./lib/write-queue");
|
const WriteQueue = require("./lib/write-queue");
|
||||||
|
const BatchRead = require("./lib/batch-read");
|
||||||
|
|
||||||
let Log;
|
let Log;
|
||||||
const now = function () { return (new Date()).getTime(); };
|
const now = function () { return (new Date()).getTime(); };
|
||||||
@@ -231,7 +232,7 @@ module.exports.create = function (cfg) {
|
|||||||
as an added bonus:
|
as an added bonus:
|
||||||
if the channel exists but its index does not then it caches the index
|
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 getIndex = (ctx, channelName, cb) => {
|
||||||
const chan = ctx.channels[channelName];
|
const chan = ctx.channels[channelName];
|
||||||
// if there is a channel in memory and it has an index cached, return it
|
// 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
|
batchIndexReads(channelName, cb, function (done) {
|
||||||
// then add the callback for the latest invocation to the queue
|
computeIndex(channelName, (err, ret) => {
|
||||||
// and wait for it to complete
|
// this is most likely an unrecoverable filesystem error
|
||||||
if (Array.isArray(indexQueues[channelName])) {
|
if (err) { return void done(err); }
|
||||||
indexQueues[channelName].push(cb);
|
// cache the computed result if possible
|
||||||
return;
|
if (chan) { chan.index = ret; }
|
||||||
}
|
// return
|
||||||
|
done(void 0, ret);
|
||||||
// otherwise, make a queue for any 'getIndex' calls made before the following 'computeIndex' call completes
|
|
||||||
var queue = indexQueues[channelName] = (indexQueues[channelName] || [cb]);
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// 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);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
61
lib/batch-read.js
Normal file
61
lib/batch-read.js
Normal file
@@ -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 nThen = require("nthen");
|
||||||
|
|
||||||
var Util = require("../../www/common/common-util");
|
var Util = require("../../www/common/common-util");
|
||||||
var Rpc = require("../../www/common/rpc");
|
|
||||||
|
|
||||||
var Nacl = require("tweetnacl");
|
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 Client = module.exports;
|
||||||
|
|
||||||
var createNetwork = Client.createNetwork = function (url, cb) {
|
var createNetwork = Client.createNetwork = function (url, cb) {
|
||||||
@@ -24,13 +14,14 @@ var createNetwork = Client.createNetwork = function (url, cb) {
|
|||||||
var info = {};
|
var info = {};
|
||||||
|
|
||||||
Netflux.connect(url, function (url) {
|
Netflux.connect(url, function (url) {
|
||||||
|
// this websocket seems to never close properly if the error is
|
||||||
|
// ECONNREFUSED
|
||||||
info.websocket = new WebSocket(url)
|
info.websocket = new WebSocket(url)
|
||||||
.on('error', function (err) {
|
.on('error', function (err) {
|
||||||
console.log(err);
|
CB(err);
|
||||||
})
|
})
|
||||||
.on('close', function (err) {
|
.on('close', function (/* err */) {
|
||||||
console.log("close");
|
delete info.websocket;
|
||||||
console.log(err);
|
|
||||||
});
|
});
|
||||||
return info.websocket;
|
return info.websocket;
|
||||||
}).then(function (network) {
|
}).then(function (network) {
|
||||||
@@ -77,7 +68,10 @@ Client.create = function (config, cb) {
|
|||||||
if (config.network) { return; }
|
if (config.network) { return; }
|
||||||
// connect to the network...
|
// connect to the network...
|
||||||
createNetwork('ws://localhost:3000/cryptpad_websocket', w(function (err, info) {
|
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.network = info.network;
|
||||||
config.websocket = info.websocket;
|
config.websocket = info.websocket;
|
||||||
}));
|
}));
|
||||||
@@ -97,21 +91,6 @@ Client.create = function (config, cb) {
|
|||||||
w.abort();
|
w.abort();
|
||||||
CB(err);
|
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 () {
|
}).nThen(function () {
|
||||||
CB(void 0, client);
|
CB(void 0, client);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -195,6 +195,10 @@ Meta.createLineHandler = function (ref, errorHandler) {
|
|||||||
|
|
||||||
return function (err, line) {
|
return function (err, line) {
|
||||||
if (err) {
|
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', {
|
return void errorHandler('METADATA_HANDLER_LINE_ERR', {
|
||||||
error: err,
|
error: err,
|
||||||
index: ref.index,
|
index: ref.index,
|
||||||
|
|||||||
20
package-lock.json
generated
20
package-lock.json
generated
@@ -98,10 +98,24 @@
|
|||||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||||
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
|
"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": {
|
"chainpad-server": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/chainpad-server/-/chainpad-server-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/chainpad-server/-/chainpad-server-3.0.5.tgz",
|
||||||
"integrity": "sha512-NRfV7FFBEYy4ZVX7h0P5znu55X8v5K4iGWeMGihkfWZLKu70GmCPUTwpBCP79dUvnCToKEa4/e8aoSPcvZC8pA==",
|
"integrity": "sha512-USKOMSHsNjnme81Qy3nQ+ji9eCkBPokYH4T82LVHAI0aayTSCXcTPUDLVGDBCRqe8NsXU4io1WPXn1KiZwB8fA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"nthen": "^0.1.8",
|
"nthen": "^0.1.8",
|
||||||
"pull-stream": "^3.6.9",
|
"pull-stream": "^3.6.9",
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
"url": "git://github.com/xwiki-labs/cryptpad.git"
|
"url": "git://github.com/xwiki-labs/cryptpad.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chainpad-server": "~3.0.2",
|
"chainpad-crypto": "^0.2.2",
|
||||||
|
"chainpad-server": "^3.0.5",
|
||||||
"express": "~4.16.0",
|
"express": "~4.16.0",
|
||||||
"fs-extra": "^7.0.0",
|
"fs-extra": "^7.0.0",
|
||||||
"get-folder-size": "^2.0.1",
|
"get-folder-size": "^2.0.1",
|
||||||
@@ -39,7 +40,7 @@
|
|||||||
"lint:less": "./node_modules/lesshint/bin/lesshint -c ./.lesshintrc ./customize.dist/src/less2/",
|
"lint:less": "./node_modules/lesshint/bin/lesshint -c ./.lesshintrc ./customize.dist/src/less2/",
|
||||||
"flow": "./node_modules/.bin/flow",
|
"flow": "./node_modules/.bin/flow",
|
||||||
"test": "node scripts/TestSelenium.js",
|
"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;"
|
"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;"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
235
rpc.js
235
rpc.js
@@ -19,6 +19,7 @@ const getFolderSize = require("get-folder-size");
|
|||||||
const Pins = require("./lib/pins");
|
const Pins = require("./lib/pins");
|
||||||
const Meta = require("./lib/metadata");
|
const Meta = require("./lib/metadata");
|
||||||
const WriteQueue = require("./lib/write-queue");
|
const WriteQueue = require("./lib/write-queue");
|
||||||
|
const BatchRead = require("./lib/batch-read");
|
||||||
|
|
||||||
var RPC = module.exports;
|
var RPC = module.exports;
|
||||||
|
|
||||||
@@ -231,6 +232,7 @@ var checkSignature = function (signedMsg, signature, publicKey) {
|
|||||||
return Nacl.sign.detached.verify(signedBuffer, signatureBuffer, pubBuffer);
|
return Nacl.sign.detached.verify(signedBuffer, signatureBuffer, pubBuffer);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const batchUserPins = BatchRead("LOAD_USER_PINS");
|
||||||
var loadUserPins = function (Env, publicKey, cb) {
|
var loadUserPins = function (Env, publicKey, cb) {
|
||||||
var session = getSession(Env.Sessions, publicKey);
|
var session = getSession(Env.Sessions, publicKey);
|
||||||
|
|
||||||
@@ -238,21 +240,23 @@ var loadUserPins = function (Env, publicKey, cb) {
|
|||||||
return cb(session.channels);
|
return cb(session.channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
var ref = {};
|
batchUserPins(publicKey, cb, function (done) {
|
||||||
var lineHandler = Pins.createLineHandler(ref, function (label, data) {
|
var ref = {};
|
||||||
Log.error(label, {
|
var lineHandler = Pins.createLineHandler(ref, function (label, data) {
|
||||||
log: publicKey,
|
Log.error(label, {
|
||||||
data: data,
|
log: publicKey,
|
||||||
|
data: data,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// if channels aren't in memory. load them from disk
|
// if channels aren't in memory. load them from disk
|
||||||
Env.pinStore.getMessages(publicKey, lineHandler, function () {
|
Env.pinStore.getMessages(publicKey, lineHandler, function () {
|
||||||
// no more messages
|
// no more messages
|
||||||
|
|
||||||
// only put this into the cache if it completes
|
// only put this into the cache if it completes
|
||||||
session.channels = ref.pins;
|
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; }
|
if (typeof(id) !== 'string' || id.length <= 2) { return null; }
|
||||||
return Path.join(root, id.slice(0, 2), id);
|
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 paths = Env.paths;
|
||||||
var path = makeFilePath(paths.blob, channel);
|
var path = makeFilePath(paths.blob, channel);
|
||||||
if (!path) {
|
if (!path) {
|
||||||
@@ -290,45 +294,49 @@ var getUploadSize = function (Env, channel, cb) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const batchFileSize = BatchRead("GET_FILE_SIZE");
|
||||||
var getFileSize = function (Env, channel, cb) {
|
var getFileSize = function (Env, channel, cb) {
|
||||||
if (!isValidId(channel)) { return void cb('INVALID_CHAN'); }
|
if (!isValidId(channel)) { return void cb('INVALID_CHAN'); }
|
||||||
|
batchFileSize(channel, cb, function (done) {
|
||||||
|
if (channel.length === 32) {
|
||||||
|
if (typeof(Env.msgStore.getChannelSize) !== 'function') {
|
||||||
|
return done('GET_CHANNEL_SIZE_UNSUPPORTED');
|
||||||
|
}
|
||||||
|
|
||||||
if (channel.length === 32) {
|
return void Env.msgStore.getChannelSize(channel, function (e, size /*:number*/) {
|
||||||
if (typeof(Env.msgStore.getChannelSize) !== 'function') {
|
if (e) {
|
||||||
return cb('GET_CHANNEL_SIZE_UNSUPPORTED');
|
if (e.code === 'ENOENT') { return void done(void 0, 0); }
|
||||||
|
return void done(e.code);
|
||||||
|
}
|
||||||
|
done(void 0, size);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return void Env.msgStore.getChannelSize(channel, function (e, size /*:number*/) {
|
// 'channel' refers to a file, so you need another API
|
||||||
if (e) {
|
getUploadSize(Env, channel, function (e, size) {
|
||||||
if (e.code === 'ENOENT') { return void cb(void 0, 0); }
|
if (typeof(size) === 'undefined') { return void done(e); }
|
||||||
return void cb(e.code);
|
done(void 0, size);
|
||||||
}
|
|
||||||
cb(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);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const batchMetadata = BatchRead("GET_METADATA");
|
||||||
var getMetadata = function (Env, channel, cb) {
|
var getMetadata = function (Env, channel, cb) {
|
||||||
if (!isValidId(channel)) { return void cb('INVALID_CHAN'); }
|
if (!isValidId(channel)) { return void cb('INVALID_CHAN'); }
|
||||||
|
|
||||||
if (channel.length !== 32) { return cb("INVALID_CHAN"); }
|
if (channel.length !== 32) { return cb("INVALID_CHAN"); }
|
||||||
|
|
||||||
var ref = {};
|
batchMetadata(channel, cb, function (done) {
|
||||||
var lineHandler = Meta.createLineHandler(ref, Log.error);
|
var ref = {};
|
||||||
|
var lineHandler = Meta.createLineHandler(ref, Log.error);
|
||||||
|
|
||||||
return void Env.msgStore.readChannelMetadata(channel, lineHandler, function (err) {
|
return void Env.msgStore.readChannelMetadata(channel, lineHandler, function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
// stream errors?
|
// 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) {
|
var getTotalSize = function (Env, publicKey, cb) {
|
||||||
var bytes = 0;
|
batchTotalSize(publicKey, cb, function (done) {
|
||||||
return void getChannelList(Env, publicKey, function (channels) {
|
var bytes = 0;
|
||||||
if (!channels) { return cb('INVALID_PIN_LIST'); } // unexpected
|
return void getChannelList(Env, publicKey, function (channels) {
|
||||||
|
if (!channels) { return done('INVALID_PIN_LIST'); } // unexpected
|
||||||
|
|
||||||
var count = channels.length;
|
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) {
|
getFileSize(Env, channel, function (e, size) {
|
||||||
count--;
|
count--;
|
||||||
if (!e) { bytes += size; }
|
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
|
// 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
|
// 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) {
|
if (config.adminEmail === false) {
|
||||||
applyCustomLimits(Env, config);
|
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 stub = id.slice(0, 2);
|
||||||
var full = makeFilePath(root, id);
|
var full = makeFilePath(root, id);
|
||||||
if (!full) {
|
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'); } */
|
/*:: if (typeof(filePath) !== 'string') { throw new Error('should never happen'); } */
|
||||||
Fs.stat(filePath, function (e, stats) {
|
Fs.stat(filePath, function (e, stats) {
|
||||||
if (e) {
|
if (e) {
|
||||||
@@ -892,8 +903,7 @@ var clearOwnedChannel = function (Env, channelId, unsafeKey, cb) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var removeOwnedBlob = function (Env, blobId, unsafeKey, cb) {
|
var removeOwnedBlob = function (Env, blobId, unsafeKey, cb) { // FIXME FILES // FIXME METADATA
|
||||||
// FIXME METADATA
|
|
||||||
var safeKey = escapeKeyCharacters(unsafeKey);
|
var safeKey = escapeKeyCharacters(unsafeKey);
|
||||||
var safeKeyPrefix = safeKey.slice(0,3);
|
var safeKeyPrefix = safeKey.slice(0,3);
|
||||||
var blobPrefix = blobId.slice(0,2);
|
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 paths = Env.paths;
|
||||||
var dec;
|
var dec;
|
||||||
try { dec = Buffer.from(content, 'base64'); }
|
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 paths = Env.paths;
|
||||||
|
|
||||||
var session = getSession(Env.Sessions, publicKey);
|
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 paths = Env.paths;
|
||||||
var session = getSession(Env.Sessions, publicKey);
|
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);
|
var oldPath = makeFilePath(paths.staging, publicKey);
|
||||||
if (!oldPath) {
|
if (!oldPath) {
|
||||||
WARN('safeMkdir', "oldPath is null"); // FIXME logging
|
WARN('safeMkdir', "oldPath is null");
|
||||||
return void cb('RENAME_ERR');
|
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 prefix = id.slice(0, 2);
|
||||||
var newPath = makeFilePath(paths.blob, id);
|
var newPath = makeFilePath(paths.blob, id);
|
||||||
if (typeof(newPath) !== 'string') {
|
if (typeof(newPath) !== 'string') {
|
||||||
WARN('safeMkdir', "newPath is null"); // FIXME logging
|
WARN('safeMkdir', "newPath is null");
|
||||||
return void cb('RENAME_ERR');
|
return void cb('RENAME_ERR');
|
||||||
}
|
}
|
||||||
|
|
||||||
Fse.mkdirp(Path.join(paths.blob, prefix), function (e) {
|
Fse.mkdirp(Path.join(paths.blob, prefix), function (e) {
|
||||||
if (e || !newPath) {
|
if (e || !newPath) {
|
||||||
WARN('safeMkdir', e); // FIXME logging
|
WARN('safeMkdir', e);
|
||||||
return void cb('RENAME_ERR');
|
return void cb('RENAME_ERR');
|
||||||
}
|
}
|
||||||
isFile(newPath, function (e, yes) {
|
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');
|
return void cb(e || 'PATH_ERR');
|
||||||
}
|
}
|
||||||
|
|
||||||
// lol wut handle ur errors
|
|
||||||
Fse.move(oldPath, newPath, function (e) {
|
Fse.move(oldPath, newPath, function (e) {
|
||||||
if (e) {
|
if (e) {
|
||||||
WARN('rename', e);
|
WARN('rename', e);
|
||||||
@@ -1135,7 +1144,7 @@ var upload_complete = function (Env, publicKey, id, cb) { // FIXME logging
|
|||||||
tryLocation(handleMove);
|
tryLocation(handleMove);
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/* FIXME FILES
|
||||||
var owned_upload_complete = function (Env, safeKey, cb) {
|
var owned_upload_complete = function (Env, safeKey, cb) {
|
||||||
var session = getSession(Env.Sessions, safeKey);
|
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);
|
var session = getSession(Env.Sessions, safeKey);
|
||||||
|
|
||||||
// the file has already been uploaded to the staging area
|
// 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;
|
var paths = Env.paths;
|
||||||
|
|
||||||
// validate that the provided size is actually a positive number
|
// 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
|
author of the block, since we assume that the block will have been
|
||||||
encrypted with xsalsa20-poly1305 which is authenticated.
|
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
|
// convert the public key to a Uint8Array and validate it
|
||||||
if (typeof(publicKey) !== 'string') { return void cb('E_INVALID_KEY'); }
|
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);
|
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
|
// prepare publicKey to be used as a file name
|
||||||
var safeKey = escapeKeyCharacters(publicKey);
|
var safeKey = escapeKeyCharacters(publicKey);
|
||||||
|
|
||||||
@@ -1442,7 +1451,7 @@ var createLoginBlockPath = function (Env, publicKey) {
|
|||||||
return Path.join(Env.paths.block, safeKey.slice(0, 2), safeKey);
|
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);
|
//console.log(msg);
|
||||||
var publicKey = msg[0];
|
var publicKey = msg[0];
|
||||||
var signature = msg[1];
|
var signature = msg[1];
|
||||||
@@ -1473,7 +1482,7 @@ var writeLoginBlock = function (Env, msg, cb) {
|
|||||||
cb(e);
|
cb(e);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}).nThen(function () { // FIXME logging
|
}).nThen(function () {
|
||||||
// actually write the block
|
// actually write the block
|
||||||
|
|
||||||
// flow is dumb and I need to guard against this which will never happen
|
// 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.
|
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 publicKey = msg[0];
|
||||||
var signature = msg[1];
|
var signature = msg[1];
|
||||||
var block = Nacl.util.decodeUTF8('DELETE_BLOCK'); // clients and the server will have to agree on this constant
|
var block = Nacl.util.decodeUTF8('DELETE_BLOCK'); // clients and the server will have to agree on this constant
|
||||||
@@ -1605,53 +1614,60 @@ var writePrivateMessage = function (Env, args, nfwssCtx, cb) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const batchDiskUsage = BatchRead("GET_DISK_USAGE");
|
||||||
var getDiskUsage = function (Env, cb) {
|
var getDiskUsage = function (Env, cb) {
|
||||||
var data = {};
|
batchDiskUsage('', cb, function (done) {
|
||||||
nThen(function (waitFor) {
|
var data = {};
|
||||||
getFolderSize('./', waitFor(function(err, info) {
|
nThen(function (waitFor) {
|
||||||
data.total = info;
|
getFolderSize('./', waitFor(function(err, info) {
|
||||||
}));
|
data.total = info;
|
||||||
getFolderSize(Env.paths.pin, waitFor(function(err, info) {
|
}));
|
||||||
data.pin = info;
|
getFolderSize(Env.paths.pin, waitFor(function(err, info) {
|
||||||
}));
|
data.pin = info;
|
||||||
getFolderSize(Env.paths.blob, waitFor(function(err, info) {
|
}));
|
||||||
data.blob = info;
|
getFolderSize(Env.paths.blob, waitFor(function(err, info) {
|
||||||
}));
|
data.blob = info;
|
||||||
getFolderSize(Env.paths.staging, waitFor(function(err, info) {
|
}));
|
||||||
data.blobstage = info;
|
getFolderSize(Env.paths.staging, waitFor(function(err, info) {
|
||||||
}));
|
data.blobstage = info;
|
||||||
getFolderSize(Env.paths.block, waitFor(function(err, info) {
|
}));
|
||||||
data.block = info;
|
getFolderSize(Env.paths.block, waitFor(function(err, info) {
|
||||||
}));
|
data.block = info;
|
||||||
getFolderSize(Env.paths.data, waitFor(function(err, info) {
|
}));
|
||||||
data.datastore = info;
|
getFolderSize(Env.paths.data, waitFor(function(err, info) {
|
||||||
}));
|
data.datastore = info;
|
||||||
}).nThen(function () {
|
}));
|
||||||
cb (void 0, data);
|
}).nThen(function () {
|
||||||
|
done(void 0, data);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const batchRegisteredUsers = BatchRead("GET_REGISTERED_USERS");
|
||||||
var getRegisteredUsers = function (Env, cb) {
|
var getRegisteredUsers = function (Env, cb) {
|
||||||
var dir = Env.paths.pin;
|
batchRegisteredUsers('', cb, function (done) {
|
||||||
var folders;
|
var dir = Env.paths.pin;
|
||||||
var users = 0;
|
var folders;
|
||||||
nThen(function (waitFor) {
|
var users = 0;
|
||||||
Fs.readdir(dir, waitFor(function (err, list) {
|
nThen(function (waitFor) {
|
||||||
if (err) {
|
|
||||||
waitFor.abort();
|
|
||||||
return void cb(err);
|
|
||||||
}
|
|
||||||
folders = list;
|
|
||||||
}));
|
|
||||||
}).nThen(function (waitFor) {
|
|
||||||
folders.forEach(function (f) {
|
|
||||||
var dir = Env.paths.pin + '/' + f;
|
|
||||||
Fs.readdir(dir, waitFor(function (err, list) {
|
Fs.readdir(dir, waitFor(function (err, list) {
|
||||||
if (err) { return; }
|
if (err) {
|
||||||
users += list.length;
|
waitFor.abort();
|
||||||
|
return void done(err);
|
||||||
|
}
|
||||||
|
folders = list;
|
||||||
}));
|
}));
|
||||||
|
}).nThen(function (waitFor) {
|
||||||
|
folders.forEach(function (f) {
|
||||||
|
var dir = Env.paths.pin + '/' + f;
|
||||||
|
Fs.readdir(dir, waitFor(function (err, list) {
|
||||||
|
if (err) { return; }
|
||||||
|
users += list.length;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}).nThen(function () {
|
||||||
|
done(void 0, users);
|
||||||
});
|
});
|
||||||
}).nThen(function () {
|
|
||||||
cb(void 0, users);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
var getActiveSessions = function (Env, ctx, cb) {
|
var getActiveSessions = function (Env, ctx, cb) {
|
||||||
@@ -1821,6 +1837,7 @@ RPC.create = function (
|
|||||||
};
|
};
|
||||||
|
|
||||||
var handleUnauthenticatedMessage = function (msg, respond, nfwssCtx) {
|
var handleUnauthenticatedMessage = function (msg, respond, nfwssCtx) {
|
||||||
|
Log.silly('LOG_RPC', msg[0]);
|
||||||
switch (msg[0]) {
|
switch (msg[0]) {
|
||||||
case 'GET_HISTORY_OFFSET': {
|
case 'GET_HISTORY_OFFSET': {
|
||||||
if (typeof(msg[1]) !== 'object' || typeof(msg[1].channelName) !== 'string') {
|
if (typeof(msg[1]) !== 'object' || typeof(msg[1].channelName) !== 'string') {
|
||||||
@@ -1844,7 +1861,7 @@ RPC.create = function (
|
|||||||
respond(e, [null, size, null]);
|
respond(e, [null, size, null]);
|
||||||
});
|
});
|
||||||
case 'GET_METADATA':
|
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]);
|
WARN(e, msg[1]);
|
||||||
respond(e, [null, data, null]);
|
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();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
1
scripts/tests/index.js
Normal file
1
scripts/tests/index.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
require("./test-rpc");
|
||||||
295
scripts/tests/test-rpc.js
Normal file
295
scripts/tests/test-rpc.js
Normal file
@@ -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([
|
(function (window) {
|
||||||
'/common/common-util.js',
|
var factory = function (Util, Crypto, Nacl) {
|
||||||
'/customize/messages.js',
|
|
||||||
'/bower_components/chainpad-crypto/crypto.js',
|
|
||||||
'/bower_components/tweetnacl/nacl-fast.min.js'
|
|
||||||
], function (Util, Messages, Crypto) {
|
|
||||||
var Nacl = window.nacl;
|
|
||||||
|
|
||||||
var Hash = window.CryptPad_Hash = {};
|
var Hash = window.CryptPad_Hash = {};
|
||||||
|
|
||||||
var uint8ArrayToHex = Util.uint8ArrayToHex;
|
var uint8ArrayToHex = Util.uint8ArrayToHex;
|
||||||
@@ -510,20 +504,6 @@ Version 1
|
|||||||
'/' + curvePublic.replace(/\//g, '-') + '/';
|
'/' + 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) {
|
Hash.isValidHref = function (href) {
|
||||||
// Non-empty href?
|
// Non-empty href?
|
||||||
if (!href) { return; }
|
if (!href) { return; }
|
||||||
@@ -547,4 +527,19 @@ Version 1
|
|||||||
};
|
};
|
||||||
|
|
||||||
return Hash;
|
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;
|
f = f || user;
|
||||||
if (f.name) {
|
if (f.name) {
|
||||||
f.displayName = f.name;
|
f.displayName = f.name;
|
||||||
|
f.edPublic = edPublic;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_owners[ed] = f || {
|
_owners[ed] = f || {
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
(function (window) {
|
(function (window) {
|
||||||
var Util = {};
|
var Util = {};
|
||||||
|
|
||||||
|
Util.tryParse = function (s) {
|
||||||
|
try { return JSON.parse(s); } catch (e) { return;}
|
||||||
|
};
|
||||||
|
|
||||||
Util.mkAsync = function (f) {
|
Util.mkAsync = function (f) {
|
||||||
return function () {
|
return function () {
|
||||||
var args = Array.prototype.slice.call(arguments);
|
var args = Array.prototype.slice.call(arguments);
|
||||||
|
|||||||
@@ -659,7 +659,6 @@ define([
|
|||||||
data.href = parsed.getUrl({present: parsed.present});
|
data.href = parsed.getUrl({present: parsed.present});
|
||||||
|
|
||||||
if (typeof (data.title) !== "string") { return cb('Missing title'); }
|
if (typeof (data.title) !== "string") { return cb('Missing title'); }
|
||||||
if (data.title.trim() === "") { data.title = Hash.getDefaultName(parsed); }
|
|
||||||
|
|
||||||
if (common.initialPath) {
|
if (common.initialPath) {
|
||||||
if (!data.path) {
|
if (!data.path) {
|
||||||
|
|||||||
@@ -563,7 +563,7 @@ define([
|
|||||||
roHref: roHref,
|
roHref: roHref,
|
||||||
atime: now,
|
atime: now,
|
||||||
ctime: 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 p = Hash.parsePadUrl(href);
|
||||||
var h = p.hashData;
|
var h = p.hashData;
|
||||||
|
|
||||||
|
if (title.trim() === "") { title = UserObject.getDefaultName(p); }
|
||||||
|
|
||||||
if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); }
|
if (AppConfig.disableAnonymousStore && !store.loggedIn) { return void cb(); }
|
||||||
if (p.type === "debug") { return void cb(); }
|
if (p.type === "debug") { return void cb(); }
|
||||||
|
|
||||||
|
|||||||
@@ -189,7 +189,6 @@ proxy.mailboxes = {
|
|||||||
box.queue.push(msg);
|
box.queue.push(msg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Crypto = Crypto;
|
|
||||||
if (!Crypto.Mailbox) {
|
if (!Crypto.Mailbox) {
|
||||||
return void console.error("chainpad-crypto is outdated and doesn't support mailboxes.");
|
return void console.error("chainpad-crypto is outdated and doesn't support mailboxes.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -695,7 +695,7 @@ define([
|
|||||||
// Fix creation time
|
// Fix creation time
|
||||||
if (!el.ctime) { el.ctime = el.atime; }
|
if (!el.ctime) { el.ctime = el.atime; }
|
||||||
// Fix title
|
// Fix title
|
||||||
if (!el.title) { el.title = Hash.getDefaultName(parsed); }
|
if (!el.title) { el.title = exp.getDefaultName(parsed); }
|
||||||
// Fix channel
|
// Fix channel
|
||||||
if (!el.channel) {
|
if (!el.channel) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,144 +1,197 @@
|
|||||||
(function () {
|
(function () {
|
||||||
var factory = function (Util, Nacl) {
|
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;
|
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 signMsg = function (data, signKey) {
|
||||||
var buffer = Nacl.util.decodeUTF8(JSON.stringify(data));
|
var buffer = Nacl.util.decodeUTF8(JSON.stringify(data));
|
||||||
return Nacl.util.encodeBase64(Nacl.sign.detached(buffer, signKey));
|
return Nacl.util.encodeBase64(Nacl.sign.detached(buffer, signKey));
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
// sendMsg takes a pre-formed message, does a little validation
|
||||||
types of messages:
|
// adds a transaction id to the message and stores its callback
|
||||||
pin -> hash
|
// and finally sends it off to the historyKeeper, which delegates its
|
||||||
unpin -> hash
|
// processing to the RPC submodule
|
||||||
getHash -> hash
|
|
||||||
getTotalSize -> bytes
|
|
||||||
getFileSize -> bytes
|
|
||||||
*/
|
|
||||||
|
|
||||||
var sendMsg = function (ctx, data, cb) {
|
var sendMsg = function (ctx, data, cb) {
|
||||||
|
if (typeof(cb) !== 'function') { throw new Error('expected callback'); }
|
||||||
|
|
||||||
var network = ctx.network;
|
var network = ctx.network;
|
||||||
var hkn = network.historyKeeper;
|
var hkn = network.historyKeeper;
|
||||||
var txid = uid();
|
if (typeof(hkn) !== 'string') { return void cb("NO_HISTORY_KEEPER"); }
|
||||||
|
|
||||||
if (typeof(cb) !== 'function') {
|
var txid = uid();
|
||||||
return console.error('expected callback');
|
|
||||||
}
|
|
||||||
|
|
||||||
var pending = ctx.pending[txid] = function (err, response) {
|
var pending = ctx.pending[txid] = function (err, response) {
|
||||||
cb(err, response);
|
cb(err, response);
|
||||||
};
|
};
|
||||||
pending.data = data;
|
pending.data = data;
|
||||||
pending.called = 0;
|
pending.called = 0;
|
||||||
|
|
||||||
return network.sendto(hkn, JSON.stringify([txid, data]));
|
return network.sendto(hkn, JSON.stringify([txid, data]));
|
||||||
};
|
};
|
||||||
|
|
||||||
var parse = function (msg) {
|
var matchesAnon = function (ctx, txid) {
|
||||||
try {
|
if (!ctx.anon) { return false; }
|
||||||
return JSON.parse(msg);
|
if (typeof(ctx.anon.pending[txid]) !== 'function') { return false; }
|
||||||
} catch (e) {
|
return true;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var onMsg = function (ctx, msg) {
|
var handleAnon = function (ctx /* anon_ctx */, txid, body /* parsed messages without txid */) {
|
||||||
var parsed = parse(msg);
|
// 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) {
|
if (!parsed) {
|
||||||
return void console.error(new Error('could not parse message: %s', msg));
|
return void console.error(new Error('could not parse message: %s', msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
// RPC messages are always arrays.
|
// RPC messages are always arrays.
|
||||||
if (!Array.isArray(parsed)) { return; }
|
if (!Array.isArray(parsed)) { return; }
|
||||||
var txid = parsed[0];
|
// ignore FULL_HISTORY messages
|
||||||
// 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 (!(parsed && parsed.slice)) {
|
|
||||||
// RPC responses are arrays. this message isn't meant for us.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (/(FULL_HISTORY|HISTORY_RANGE)/.test(parsed[0])) { return; }
|
if (/(FULL_HISTORY|HISTORY_RANGE)/.test(parsed[0])) { return; }
|
||||||
|
|
||||||
var response = parsed.slice(2);
|
var txid = parsed[0];
|
||||||
|
|
||||||
if (typeof(pending) === 'function') {
|
// txid must be a string, or this message is not meant for us
|
||||||
if (parsed[1] === 'ERROR') {
|
if (typeof(txid) !== 'string') { return; }
|
||||||
if (parsed[2] === 'NO_COOKIE') {
|
|
||||||
return void ctx.send('COOKIE', "", function (e) {
|
|
||||||
if (e) {
|
|
||||||
console.error(e);
|
|
||||||
return void pending(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// resend the same command again
|
if (matchesAnon(ctx, txid)) {
|
||||||
// give up if you've already tried resending
|
return void handleAnon(ctx.anon, txid, parsed.slice(1));
|
||||||
if (ctx.resend(txid)) { delete ctx.pending[txid]; }
|
}
|
||||||
});
|
|
||||||
|
// 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];
|
||||||
}
|
}
|
||||||
|
pending(void 0, parsed.slice(2));
|
||||||
|
|
||||||
pending(parsed[2]);
|
// if successful, delete the callback...
|
||||||
delete ctx.pending[txid];
|
delete rpc_ctx.pending[txid];
|
||||||
return;
|
// prevent further iteration
|
||||||
} else {
|
return true;
|
||||||
// update the cookie
|
|
||||||
if (/\|/.test(cookie)) {
|
|
||||||
if (ctx.cookie !== cookie) {
|
|
||||||
ctx.cookie = cookie;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pending(void 0, response);
|
|
||||||
|
|
||||||
// if successful, delete the callback...
|
// NO_COOKIE errors mean you failed to authenticate.
|
||||||
delete ctx.pending[txid];
|
// request a new cookie and resend the query
|
||||||
|
if (parsed[2] === 'NO_COOKIE') {
|
||||||
|
rpc_ctx.send('COOKIE', "", function (e) {
|
||||||
|
if (e) {
|
||||||
|
console.error(e);
|
||||||
|
return void pending(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// resend the same command again
|
||||||
|
// give up if you've already tried resending
|
||||||
|
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]);
|
||||||
|
// and delete the pending callback
|
||||||
|
delete rpc_ctx.pending[txid];
|
||||||
|
|
||||||
|
// prevent further iteration
|
||||||
|
return true;
|
||||||
|
})) {
|
||||||
|
// the message was handled, so stop here
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// HACK to hide messages from the anon rpc
|
console.error("UNHANDLED RPC MESSAGE", msg);
|
||||||
if (parsed.length !== 4 && parsed[1] !== 'ERROR') {
|
|
||||||
console.log(parsed);
|
|
||||||
console.error("received message [%s] for txid[%s] with no callback", msg, txid);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var create = function (network, edPrivateKey, edPublicKey, cb) {
|
var networks = [];
|
||||||
var signKey;
|
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 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
var initNetworkContext = function (network) {
|
||||||
var ctx = {
|
var ctx = {
|
||||||
network: network,
|
network: network,
|
||||||
timeouts: {}, // timeouts
|
connected: true,
|
||||||
pending: {}, // callbacks
|
anon: undefined,
|
||||||
|
authenticated: [],
|
||||||
|
};
|
||||||
|
networks.push(network);
|
||||||
|
contexts.push(ctx);
|
||||||
|
|
||||||
|
// 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: networkContext.network,
|
||||||
|
publicKey: keys.publicKeyString,
|
||||||
|
timeouts: {},
|
||||||
|
pending: {},
|
||||||
cookie: null,
|
cookie: null,
|
||||||
connected: true,
|
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') {
|
if (!ctx.connected && type !== 'COOKIE') {
|
||||||
return void setTimeout(function () {
|
return void cb("DISCONNECTED");
|
||||||
cb('DISCONNECTED');
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// construct a signed message...
|
// construct a signed message...
|
||||||
@@ -150,9 +203,9 @@ types of messages:
|
|||||||
data.unshift(ctx.cookie);
|
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);
|
data.unshift(sig);
|
||||||
|
|
||||||
// [sig, edPublicKey, cookie, type, msg]
|
// [sig, edPublicKey, cookie, type, msg]
|
||||||
@@ -169,25 +222,29 @@ types of messages:
|
|||||||
|
|
||||||
// update the cookie and signature...
|
// update the cookie and signature...
|
||||||
pending.data[2] = ctx.cookie;
|
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 {
|
try {
|
||||||
return ctx.network.sendto(ctx.network.historyKeeper,
|
return ctx.network.sendto(ctx.network.historyKeeper,
|
||||||
JSON.stringify([txid, pending.data]));
|
JSON.stringify([new_txid, pending.data]));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("failed to resend");
|
console.log("failed to resend");
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
send.unauthenticated = function (type, msg, cb) {
|
send.unauthenticated = function (type, msg, _cb) {
|
||||||
if (!ctx.connected) {
|
var cb = Util.mkAsync(_cb);
|
||||||
return void setTimeout(function () {
|
if (!ctx.connected) { return void cb('DISCONNECTED'); }
|
||||||
cb('DISCONNECTED');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// construct an unsigned message
|
// 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) {
|
if (ctx.cookie && ctx.cookie.join) {
|
||||||
data[2] = ctx.cookie.join('|');
|
data[2] = ctx.cookie.join('|');
|
||||||
} else {
|
} else {
|
||||||
@@ -197,103 +254,100 @@ types of messages:
|
|||||||
return sendMsg(ctx, data, cb);
|
return sendMsg(ctx, data, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
network.on('message', function (msg, sender) {
|
ctx.destroy = function () {
|
||||||
if (sender !== network.historyKeeper) { return; }
|
// clear all pending timeouts
|
||||||
onMsg(ctx, msg);
|
Object.keys(ctx.timeouts).forEach(function (to) {
|
||||||
});
|
clearTimeout(to);
|
||||||
|
|
||||||
network.on('disconnect', function () {
|
|
||||||
ctx.connected = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
network.on('reconnect', function () {
|
|
||||||
send('COOKIE', "", function (e) {
|
|
||||||
if (e) { return void cb(e); }
|
|
||||||
ctx.connected = true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// remove the ctx from the network's stack
|
||||||
|
var idx = networkContext.authenticated.indexOf(ctx);
|
||||||
|
if (idx === -1) { return; }
|
||||||
|
networkContext.authenticated.splice(idx, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
networkContext.authenticated.push(ctx);
|
||||||
|
return ctx;
|
||||||
|
};
|
||||||
|
|
||||||
|
var getAuthenticatedContext = function (networkContext, keys) {
|
||||||
|
if (!networkContext) { throw new Error('expected network context'); }
|
||||||
|
|
||||||
|
var publicKey = keys.publicKeyString;
|
||||||
|
|
||||||
|
var i;
|
||||||
|
networkContext.authenticated.some(function (ctx, j) {
|
||||||
|
if (ctx.publicKey !== publicKey) { return false; }
|
||||||
|
i = j;
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
// network.onHistoryKeeperChange is defined in chainpad-netflux.js
|
if (networkContext.authenticated[i]) { return networkContext.authenticated[i]; }
|
||||||
// The function we pass will be called when the drive reconnects and
|
|
||||||
// chainpad-netflux detects a new history keeper id
|
return initAuthenticatedRpc(networkContext, keys);
|
||||||
if (network.onHistoryKeeperChange) {
|
};
|
||||||
network.onHistoryKeeperChange(function () {
|
|
||||||
send('COOKIE', "", function (e) {
|
var create = function (network, edPrivateKey, edPublicKey, _cb) {
|
||||||
if (e) { return void cb(e); }
|
if (typeof(_cb) !== 'function') { throw new Error("expected callback"); }
|
||||||
ctx.connected = true;
|
|
||||||
});
|
var cb = Util.mkAsync(_cb);
|
||||||
});
|
|
||||||
|
var signKey;
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
send('COOKIE', "", function (e) {
|
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); }
|
if (e) { return void cb(e); }
|
||||||
// callback to provide 'send' method to whatever needs it
|
// callback to provide 'send' method to whatever needs it
|
||||||
cb(void 0, { send: send, });
|
cb(void 0, {
|
||||||
|
send: rpc_ctx.send,
|
||||||
|
destroy: rpc_ctx.destroy,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var onAnonMsg = function (ctx, msg) {
|
var initAnonRpc = function (networkContext) {
|
||||||
var parsed = parse(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; }
|
|
||||||
var txid = parsed[0];
|
|
||||||
|
|
||||||
// txid must be a string, or this message is not meant for us
|
|
||||||
if (typeof(txid) !== 'string') { return; }
|
|
||||||
|
|
||||||
var pending = ctx.pending[txid];
|
|
||||||
|
|
||||||
if (!(parsed && parsed.slice)) {
|
|
||||||
// RPC responses are arrays. this message isn't meant for us.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var createAnonymous = function (network, cb) {
|
|
||||||
var ctx = {
|
var ctx = {
|
||||||
network: network,
|
network: networkContext.network,
|
||||||
timeouts: {}, // timeouts
|
timeouts: {},
|
||||||
pending: {}, // callbacks
|
pending: {},
|
||||||
cookie: null,
|
|
||||||
connected: true,
|
connected: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
var send = ctx.send = function (type, msg, cb) {
|
// any particular network will only ever need one anonymous rpc
|
||||||
if (!ctx.connected) {
|
networkContext.anon = ctx;
|
||||||
return void setTimeout(function () {
|
|
||||||
cb('DISCONNECTED');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
ctx.send = function (type, msg, _cb) {
|
||||||
|
var cb = Util.mkAsync(_cb);
|
||||||
|
if (!ctx.connected) { return void cb('DISCONNECTED'); }
|
||||||
|
|
||||||
// construct an unsigned message...
|
// construct an unsigned message...
|
||||||
var data = [type, msg];
|
var data = [type, msg];
|
||||||
|
|
||||||
// [sig, edPublicKey, cookie, type, msg]
|
// [type, msg]
|
||||||
return sendMsg(ctx, data, cb);
|
return sendMsg(ctx, data, cb);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -314,21 +368,35 @@ types of messages:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
network.on('message', function (msg, sender) {
|
ctx.destroy = function () {
|
||||||
if (sender !== network.historyKeeper) { return; }
|
// clear all pending timeouts
|
||||||
onAnonMsg(ctx, msg);
|
Object.keys(ctx.timeouts).forEach(function (to) {
|
||||||
});
|
clearTimeout(to);
|
||||||
|
});
|
||||||
|
|
||||||
network.on('disconnect', function () {
|
networkContext.anon = undefined;
|
||||||
ctx.connected = false;
|
};
|
||||||
});
|
|
||||||
|
|
||||||
network.on('reconnect', function () {
|
return ctx;
|
||||||
ctx.connected = true;
|
};
|
||||||
});
|
|
||||||
|
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, {
|
cb(void 0, {
|
||||||
send: send
|
send: ctx.send,
|
||||||
|
destroy: ctx.destroy,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -50,9 +50,10 @@ define([
|
|||||||
'/common/outer/local-store.js',
|
'/common/outer/local-store.js',
|
||||||
'/customize/application_config.js',
|
'/customize/application_config.js',
|
||||||
'/common/test.js',
|
'/common/test.js',
|
||||||
|
'/common/userObject.js',
|
||||||
], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel,
|
], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel,
|
||||||
_FilePicker, _Share, _Messaging, _Notifier, _Hash, _Util, _Realtime,
|
_FilePicker, _Share, _Messaging, _Notifier, _Hash, _Util, _Realtime,
|
||||||
_Constants, _Feedback, _LocalStore, _AppConfig, _Test) {
|
_Constants, _Feedback, _LocalStore, _AppConfig, _Test, _UserObject) {
|
||||||
CpNfOuter = _CpNfOuter;
|
CpNfOuter = _CpNfOuter;
|
||||||
Cryptpad = _Cryptpad;
|
Cryptpad = _Cryptpad;
|
||||||
Crypto = Utils.Crypto = _Crypto;
|
Crypto = Utils.Crypto = _Crypto;
|
||||||
@@ -68,6 +69,7 @@ define([
|
|||||||
Utils.Constants = _Constants;
|
Utils.Constants = _Constants;
|
||||||
Utils.Feedback = _Feedback;
|
Utils.Feedback = _Feedback;
|
||||||
Utils.LocalStore = _LocalStore;
|
Utils.LocalStore = _LocalStore;
|
||||||
|
Utils.UserObject = _UserObject;
|
||||||
AppConfig = _AppConfig;
|
AppConfig = _AppConfig;
|
||||||
Test = _Test;
|
Test = _Test;
|
||||||
|
|
||||||
@@ -271,7 +273,7 @@ define([
|
|||||||
Utils.crypto = Utils.Crypto.createEncryptor(Utils.secret.keys);
|
Utils.crypto = Utils.Crypto.createEncryptor(Utils.secret.keys);
|
||||||
var parsed = Utils.Hash.parsePadUrl(window.location.href);
|
var parsed = Utils.Hash.parsePadUrl(window.location.href);
|
||||||
if (!parsed.type) { throw new Error(); }
|
if (!parsed.type) { throw new Error(); }
|
||||||
var defaultTitle = Utils.Hash.getDefaultName(parsed);
|
var defaultTitle = Utils.UserObject.getDefaultName(parsed);
|
||||||
var edPublic, curvePublic, notifications, isTemplate;
|
var edPublic, curvePublic, notifications, isTemplate;
|
||||||
var forceCreationScreen = cfg.useCreationScreen &&
|
var forceCreationScreen = cfg.useCreationScreen &&
|
||||||
sessionStorage[Utils.Constants.displayPadCreationScreen];
|
sessionStorage[Utils.Constants.displayPadCreationScreen];
|
||||||
@@ -1176,7 +1178,7 @@ define([
|
|||||||
|
|
||||||
// Update metadata values and send new metadata inside
|
// Update metadata values and send new metadata inside
|
||||||
parsed = Utils.Hash.parsePadUrl(window.location.href);
|
parsed = Utils.Hash.parsePadUrl(window.location.href);
|
||||||
defaultTitle = Utils.Hash.getDefaultName(parsed);
|
defaultTitle = Utils.UserObject.getDefaultName(parsed);
|
||||||
hashes = Utils.Hash.getHashes(secret);
|
hashes = Utils.Hash.getHashes(secret);
|
||||||
readOnly = false;
|
readOnly = false;
|
||||||
updateMeta();
|
updateMeta();
|
||||||
|
|||||||
@@ -15,8 +15,24 @@ define([
|
|||||||
var TEMPLATE = module.TEMPLATE = "template";
|
var TEMPLATE = module.TEMPLATE = "template";
|
||||||
var SHARED_FOLDERS = module.SHARED_FOLDERS = "sharedFolders";
|
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) {
|
module.init = function (files, config) {
|
||||||
var exp = {};
|
var exp = {};
|
||||||
|
exp.getDefaultName = module.getDefaultName;
|
||||||
|
|
||||||
var sframeChan = config.sframeChan;
|
var sframeChan = config.sframeChan;
|
||||||
|
|
||||||
var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Constants.storageKey;
|
var FILES_DATA = module.FILES_DATA = exp.FILES_DATA = Constants.storageKey;
|
||||||
|
|||||||
Reference in New Issue
Block a user