mirror of https://github.com/xwiki-labs/cryptpad
stop relying on netflux-server internals
* create RPC module from inside historyKeeper * stop passing around netflux-server context * update to use newer netflux-server's formal APIs * manage your own cache of indexes instead of storing things in the netflux context
This commit is contained in:
parent
06c29ef1d1
commit
779e817443
66
lib/api.js
66
lib/api.js
|
@ -4,8 +4,6 @@ const WebSocketServer = require('ws').Server;
|
||||||
const NetfluxSrv = require('chainpad-server');
|
const NetfluxSrv = require('chainpad-server');
|
||||||
|
|
||||||
module.exports.create = function (config) {
|
module.exports.create = function (config) {
|
||||||
var rpc;
|
|
||||||
const log = config.log;
|
|
||||||
const wsConfig = {
|
const wsConfig = {
|
||||||
server: config.httpServer,
|
server: config.httpServer,
|
||||||
};
|
};
|
||||||
|
@ -32,35 +30,45 @@ module.exports.create = function (config) {
|
||||||
});
|
});
|
||||||
}, 1000 * 60 * 5); // run every five minutes
|
}, 1000 * 60 * 5); // run every five minutes
|
||||||
}));
|
}));
|
||||||
}).nThen(function (w) {
|
|
||||||
require("./rpc").create(config, w(function (e, _rpc) {
|
|
||||||
if (e) {
|
|
||||||
w.abort();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
rpc = _rpc;
|
|
||||||
}));
|
|
||||||
}).nThen(function () {
|
}).nThen(function () {
|
||||||
var HK = require('./historyKeeper.js');
|
// asynchronously create a historyKeeper and RPC together
|
||||||
var hkConfig = {
|
require('./historyKeeper.js').create(config, function (err, historyKeeper) {
|
||||||
tasks: config.tasks,
|
if (err) { throw err; }
|
||||||
rpc: rpc,
|
|
||||||
store: config.store,
|
|
||||||
log: log,
|
|
||||||
};
|
|
||||||
|
|
||||||
var historyKeeper = HK.create(hkConfig);
|
var log = config.log;
|
||||||
|
|
||||||
NetfluxSrv.create(new WebSocketServer(wsConfig))
|
// spawn ws server and attach netflux event handlers
|
||||||
.on('channelClose', historyKeeper.channelClose)
|
NetfluxSrv.create(new WebSocketServer(wsConfig))
|
||||||
.on('channelMessage', historyKeeper.channelMessage)
|
.on('channelClose', historyKeeper.channelClose)
|
||||||
.on('channelOpen', historyKeeper.channelOpen)
|
.on('channelMessage', historyKeeper.channelMessage)
|
||||||
.on('sessionClose', function (userId, reason) {
|
.on('channelOpen', historyKeeper.channelOpen)
|
||||||
reason = reason; // XXX
|
.on('sessionClose', function (userId, reason) {
|
||||||
})
|
if (['BAD_MESSAGE', 'SOCKET_ERROR', 'SEND_MESSAGE_FAIL_2'].indexOf(reason) !== -1) {
|
||||||
.on('error', function (error, label, info) {
|
return void log.error('SESSION_CLOSE_WITH_ERROR', {
|
||||||
info = info; // XXX
|
userId: userId,
|
||||||
})
|
reason: reason,
|
||||||
.register(historyKeeper.id, historyKeeper.directMessage);
|
});
|
||||||
|
}
|
||||||
|
log.verbose('SESSION_CLOSE_ROUTINE', {
|
||||||
|
userId: userId,
|
||||||
|
reason: reason,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.on('error', function (error, label, info) {
|
||||||
|
if (!error) { return; }
|
||||||
|
/* labels:
|
||||||
|
SEND_MESSAGE_FAIL, SEND_MESSAGE_FAIL_2, FAIL_TO_DISCONNECT,
|
||||||
|
FAIL_TO_TERMINATE, HANDLE_CHANNEL_LEAVE, NETFLUX_BAD_MESSAGE,
|
||||||
|
NETFLUX_WEBSOCKET_ERROR
|
||||||
|
*/
|
||||||
|
log.error(label, {
|
||||||
|
code: error.code,
|
||||||
|
message: error.message,
|
||||||
|
stack: error.stack,
|
||||||
|
info: info,
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.register(historyKeeper.id, historyKeeper.directMessage);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,25 +6,15 @@ var Fs = require("fs");
|
||||||
|
|
||||||
var Admin = module.exports;
|
var Admin = module.exports;
|
||||||
|
|
||||||
var getActiveSessions = function (Env, ctx, cb) {
|
var getActiveSessions = function (Env, Server, cb) {
|
||||||
var total = ctx.users ? Object.keys(ctx.users).length : '?';
|
var stats = Server.getSessionStats();
|
||||||
|
cb(void 0, [
|
||||||
var ips = [];
|
stats.total,
|
||||||
Object.keys(ctx.users).forEach(function (u) {
|
stats.unique
|
||||||
var user = ctx.users[u];
|
]);
|
||||||
var socket = user.socket;
|
|
||||||
var req = socket.upgradeReq;
|
|
||||||
var conn = req && req.connection;
|
|
||||||
var ip = (req && req.headers && req.headers['x-forwarded-for']) || (conn && conn.remoteAddress);
|
|
||||||
if (ip && ips.indexOf(ip) === -1) {
|
|
||||||
ips.push(ip);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
cb (void 0, [total, ips.length]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var shutdown = function (Env, ctx, cb) {
|
var shutdown = function (Env, Server, cb) {
|
||||||
return void cb('E_NOT_IMPLEMENTED');
|
return void cb('E_NOT_IMPLEMENTED');
|
||||||
//clearInterval(Env.sessionExpirationInterval);
|
//clearInterval(Env.sessionExpirationInterval);
|
||||||
// XXX set a flag to prevent incoming database writes
|
// XXX set a flag to prevent incoming database writes
|
||||||
|
@ -91,19 +81,18 @@ var getDiskUsage = function (Env, cb) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Admin.command = function (Env, Server, publicKey, data, cb) {
|
||||||
|
|
||||||
Admin.command = function (Env, ctx, publicKey, data, cb) {
|
|
||||||
var admins = Env.admins;
|
var admins = Env.admins;
|
||||||
if (admins.indexOf(publicKey) === -1) {
|
if (admins.indexOf(publicKey) === -1) {
|
||||||
return void cb("FORBIDDEN");
|
return void cb("FORBIDDEN");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle commands here
|
// Handle commands here
|
||||||
switch (data[0]) {
|
switch (data[0]) {
|
||||||
case 'ACTIVE_SESSIONS':
|
case 'ACTIVE_SESSIONS':
|
||||||
return getActiveSessions(Env, ctx, cb);
|
return getActiveSessions(Env, Server, cb);
|
||||||
case 'ACTIVE_PADS':
|
case 'ACTIVE_PADS':
|
||||||
return cb(void 0, ctx.channels ? Object.keys(ctx.channels).length : '?');
|
return cb(void 0, Server.getActiveChannelCount());
|
||||||
case 'REGISTERED_USERS':
|
case 'REGISTERED_USERS':
|
||||||
return getRegisteredUsers(Env, cb);
|
return getRegisteredUsers(Env, cb);
|
||||||
case 'DISK_USAGE':
|
case 'DISK_USAGE':
|
||||||
|
@ -112,7 +101,7 @@ Admin.command = function (Env, ctx, publicKey, data, cb) {
|
||||||
Env.flushCache();
|
Env.flushCache();
|
||||||
return cb(void 0, true);
|
return cb(void 0, true);
|
||||||
case 'SHUTDOWN':
|
case 'SHUTDOWN':
|
||||||
return shutdown(Env, ctx, cb);
|
return shutdown(Env, Server, cb);
|
||||||
default:
|
default:
|
||||||
return cb('UNHANDLED_ADMIN_COMMAND');
|
return cb('UNHANDLED_ADMIN_COMMAND');
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,7 +147,7 @@ Channel.isNewChannel = function (Env, channel, cb) {
|
||||||
|
|
||||||
Otherwise behaves the same as sending to a channel
|
Otherwise behaves the same as sending to a channel
|
||||||
*/
|
*/
|
||||||
Channel.writePrivateMessage = function (Env, args, nfwssCtx, cb) {
|
Channel.writePrivateMessage = function (Env, args, Server, cb) {
|
||||||
var channelId = args[0];
|
var channelId = args[0];
|
||||||
var msg = args[1];
|
var msg = args[1];
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ Channel.writePrivateMessage = function (Env, args, nfwssCtx, cb) {
|
||||||
|
|
||||||
// We expect a modern netflux-websocket-server instance
|
// We expect a modern netflux-websocket-server instance
|
||||||
// if this API isn't here everything will fall apart anyway
|
// if this API isn't here everything will fall apart anyway
|
||||||
if (!(nfwssCtx && nfwssCtx.historyKeeper && typeof(nfwssCtx.historyKeeper.onChannelMessage) === 'function')) {
|
if (!(Server && typeof(Server.send) === 'function')) {
|
||||||
return void cb("NOT_IMPLEMENTED");
|
return void cb("NOT_IMPLEMENTED");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,8 +180,9 @@ Channel.writePrivateMessage = function (Env, args, nfwssCtx, cb) {
|
||||||
msg // the actual message content. Generally a string
|
msg // the actual message content. Generally a string
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// XXX this API doesn't exist anymore...
|
||||||
// store the message and do everything else that is typically done when going through historyKeeper
|
// store the message and do everything else that is typically done when going through historyKeeper
|
||||||
nfwssCtx.historyKeeper.onChannelMessage(nfwssCtx, channelStruct, fullMessage);
|
Env.historyKeeper.onChannelMessage(Server, channelStruct, fullMessage);
|
||||||
|
|
||||||
// call back with the message and the target channel.
|
// call back with the message and the target channel.
|
||||||
// historyKeeper will take care of broadcasting it if anyone is in the channel
|
// historyKeeper will take care of broadcasting it if anyone is in the channel
|
||||||
|
|
|
@ -9,6 +9,8 @@ const Meta = require("./metadata");
|
||||||
const WriteQueue = require("./write-queue");
|
const WriteQueue = require("./write-queue");
|
||||||
const BatchRead = require("./batch-read");
|
const BatchRead = require("./batch-read");
|
||||||
|
|
||||||
|
const RPC = require("./rpc");
|
||||||
|
|
||||||
const Extras = require("./hk-util.js");
|
const Extras = require("./hk-util.js");
|
||||||
const STANDARD_CHANNEL_LENGTH = Extras.STANDARD_CHANNEL_LENGTH;
|
const STANDARD_CHANNEL_LENGTH = Extras.STANDARD_CHANNEL_LENGTH;
|
||||||
const EPHEMERAL_CHANNEL_LENGTH = Extras.EPHEMERAL_CHANNEL_LENGTH;
|
const EPHEMERAL_CHANNEL_LENGTH = Extras.EPHEMERAL_CHANNEL_LENGTH;
|
||||||
|
@ -66,8 +68,8 @@ const isValidValidateKeyString = function (key) {
|
||||||
|
|
||||||
var CHECKPOINT_PATTERN = /^cp\|(([A-Za-z0-9+\/=]+)\|)?/;
|
var CHECKPOINT_PATTERN = /^cp\|(([A-Za-z0-9+\/=]+)\|)?/;
|
||||||
|
|
||||||
module.exports.create = function (cfg) {
|
module.exports.create = function (cfg, cb) {
|
||||||
const rpc = cfg.rpc;
|
var rpc;
|
||||||
const tasks = cfg.tasks;
|
const tasks = cfg.tasks;
|
||||||
const store = cfg.store;
|
const store = cfg.store;
|
||||||
Log = cfg.log;
|
Log = cfg.log;
|
||||||
|
@ -75,6 +77,7 @@ module.exports.create = function (cfg) {
|
||||||
Log.silly('HK_LOADING', 'LOADING HISTORY_KEEPER MODULE');
|
Log.silly('HK_LOADING', 'LOADING HISTORY_KEEPER MODULE');
|
||||||
|
|
||||||
const metadata_cache = {};
|
const metadata_cache = {};
|
||||||
|
const channel_cache = {};
|
||||||
const HISTORY_KEEPER_ID = Crypto.randomBytes(8).toString('hex');
|
const HISTORY_KEEPER_ID = Crypto.randomBytes(8).toString('hex');
|
||||||
|
|
||||||
Log.verbose('HK_ID', 'History keeper ID: ' + HISTORY_KEEPER_ID);
|
Log.verbose('HK_ID', 'History keeper ID: ' + HISTORY_KEEPER_ID);
|
||||||
|
@ -211,8 +214,9 @@ module.exports.create = function (cfg) {
|
||||||
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 batchIndexReads = BatchRead("HK_GET_INDEX");
|
const batchIndexReads = BatchRead("HK_GET_INDEX");
|
||||||
const getIndex = (ctx, channelName, cb) => {
|
const getIndex = (channelName, cb) => {
|
||||||
const chan = ctx.channels[channelName];
|
const chan = channel_cache[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
|
||||||
if (chan && chan.index) {
|
if (chan && chan.index) {
|
||||||
// enforce async behaviour
|
// enforce async behaviour
|
||||||
|
@ -233,15 +237,7 @@ module.exports.create = function (cfg) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/*::
|
|
||||||
type cp_index_item = {
|
|
||||||
offset: number,
|
|
||||||
line: number
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* storeMessage
|
/* storeMessage
|
||||||
* ctx
|
|
||||||
* channel id
|
* channel id
|
||||||
* the message to store
|
* the message to store
|
||||||
* whether the message is a checkpoint
|
* whether the message is a checkpoint
|
||||||
|
@ -260,7 +256,7 @@ module.exports.create = function (cfg) {
|
||||||
*/
|
*/
|
||||||
const queueStorage = WriteQueue();
|
const queueStorage = WriteQueue();
|
||||||
|
|
||||||
const storeMessage = function (ctx, channel, msg, isCp, optionalMessageHash) {
|
const storeMessage = function (channel, msg, isCp, optionalMessageHash) {
|
||||||
const id = channel.id;
|
const id = channel.id;
|
||||||
|
|
||||||
queueStorage(id, function (next) {
|
queueStorage(id, function (next) {
|
||||||
|
@ -284,7 +280,7 @@ module.exports.create = function (cfg) {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}).nThen((waitFor) => {
|
}).nThen((waitFor) => {
|
||||||
getIndex(ctx, id, waitFor((err, index) => {
|
getIndex(id, waitFor((err, index) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
Log.warn("HK_STORE_MESSAGE_INDEX", err.stack);
|
Log.warn("HK_STORE_MESSAGE_INDEX", err.stack);
|
||||||
// non-critical, we'll be able to get the channel index later
|
// non-critical, we'll be able to get the channel index later
|
||||||
|
@ -298,10 +294,10 @@ module.exports.create = function (cfg) {
|
||||||
delete index.offsetByHash[k];
|
delete index.offsetByHash[k];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
index.cpIndex.push(({
|
index.cpIndex.push({
|
||||||
offset: index.size,
|
offset: index.size,
|
||||||
line: ((index.line || 0) + 1)
|
line: ((index.line || 0) + 1)
|
||||||
} /*:cp_index_item*/));
|
});
|
||||||
}
|
}
|
||||||
if (optionalMessageHash) { index.offsetByHash[optionalMessageHash] = index.size; }
|
if (optionalMessageHash) { index.offsetByHash[optionalMessageHash] = index.size; }
|
||||||
index.size += msgBin.length;
|
index.size += msgBin.length;
|
||||||
|
@ -313,21 +309,10 @@ module.exports.create = function (cfg) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/* historyKeeperBroadcast
|
|
||||||
* uses API from the netflux server to send messages to every member of a channel
|
|
||||||
* sendMsg runs in a try-catch and drops users if sending a message fails
|
|
||||||
*/
|
|
||||||
const historyKeeperBroadcast = function (ctx, channel, msg) {
|
|
||||||
let chan = ctx.channels[channel] || (([] /*:any*/) /*:Chan_t*/);
|
|
||||||
chan.forEach(function (user) {
|
|
||||||
ctx.sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(msg)]);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/* expireChannel is here to clean up channels that should have been removed
|
/* expireChannel is here to clean up channels that should have been removed
|
||||||
but for some reason are still present
|
but for some reason are still present
|
||||||
*/
|
*/
|
||||||
const expireChannel = function (ctx, channel) {
|
const expireChannel = function (channel) {
|
||||||
return void store.archiveChannel(channel, function (err) {
|
return void store.archiveChannel(channel, function (err) {
|
||||||
Log.info("ARCHIVAL_CHANNEL_BY_HISTORY_KEEPER_EXPIRATION", {
|
Log.info("ARCHIVAL_CHANNEL_BY_HISTORY_KEEPER_EXPIRATION", {
|
||||||
channelId: channel,
|
channelId: channel,
|
||||||
|
@ -336,6 +321,14 @@ module.exports.create = function (cfg) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* dropChannel
|
||||||
|
* cleans up memory structures which are managed entirely by the historyKeeper
|
||||||
|
*/
|
||||||
|
const dropChannel = function (chanName) {
|
||||||
|
delete metadata_cache[chanName];
|
||||||
|
delete channel_cache[chanName];
|
||||||
|
};
|
||||||
|
|
||||||
/* checkExpired
|
/* checkExpired
|
||||||
* synchronously returns true or undefined to indicate whether the channel is expired
|
* synchronously returns true or undefined to indicate whether the channel is expired
|
||||||
* according to its metadata
|
* according to its metadata
|
||||||
|
@ -347,7 +340,7 @@ module.exports.create = function (cfg) {
|
||||||
|
|
||||||
FIXME the boolean nature of this API should be separated from its side effects
|
FIXME the boolean nature of this API should be separated from its side effects
|
||||||
*/
|
*/
|
||||||
const checkExpired = function (ctx, channel) {
|
const checkExpired = function (Server, channel) {
|
||||||
if (!(channel && channel.length === STANDARD_CHANNEL_LENGTH)) { return false; }
|
if (!(channel && channel.length === STANDARD_CHANNEL_LENGTH)) { return false; }
|
||||||
let metadata = metadata_cache[channel];
|
let metadata = metadata_cache[channel];
|
||||||
if (!(metadata && typeof(metadata.expire) === 'number')) { return false; }
|
if (!(metadata && typeof(metadata.expire) === 'number')) { return false; }
|
||||||
|
@ -362,18 +355,16 @@ module.exports.create = function (cfg) {
|
||||||
// there may have been a problem with scheduling tasks
|
// there may have been a problem with scheduling tasks
|
||||||
// or the scheduled tasks may not be running
|
// or the scheduled tasks may not be running
|
||||||
// so trigger a removal from here
|
// so trigger a removal from here
|
||||||
if (pastDue >= ONE_DAY) { expireChannel(ctx, channel); }
|
if (pastDue >= ONE_DAY) { expireChannel(channel); }
|
||||||
|
|
||||||
// close the channel
|
// close the channel
|
||||||
store.closeChannel(channel, function () {
|
store.closeChannel(channel, function () {
|
||||||
historyKeeperBroadcast(ctx, channel, {
|
// XXX make sure that clients actually disconnect when we broadcast an error
|
||||||
|
Server.channelBroadcast(channel, {
|
||||||
error: 'EEXPIRED',
|
error: 'EEXPIRED',
|
||||||
channel: channel
|
channel: channel
|
||||||
});
|
}, HISTORY_KEEPER_ID);
|
||||||
// remove it from any caches after you've told anyone in the channel
|
dropChannel(channel);
|
||||||
// that it has expired
|
|
||||||
delete ctx.channels[channel];
|
|
||||||
delete metadata_cache[channel];
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// return true to indicate that it has expired
|
// return true to indicate that it has expired
|
||||||
|
@ -391,7 +382,7 @@ module.exports.create = function (cfg) {
|
||||||
* adds timestamps to incoming messages
|
* adds timestamps to incoming messages
|
||||||
* writes messages to the store
|
* writes messages to the store
|
||||||
*/
|
*/
|
||||||
const onChannelMessage = function (ctx, channel, msgStruct) {
|
const onChannelMessage = function (Server, channel, msgStruct) {
|
||||||
// TODO our usage of 'channel' here looks prone to errors
|
// TODO our usage of 'channel' here looks prone to errors
|
||||||
// we only use it for its 'id', but it can contain other stuff
|
// we only use it for its 'id', but it can contain other stuff
|
||||||
// also, we're using this RPC from both the RPC and Netflux-server
|
// also, we're using this RPC from both the RPC and Netflux-server
|
||||||
|
@ -414,7 +405,7 @@ module.exports.create = function (cfg) {
|
||||||
let metadata;
|
let metadata;
|
||||||
nThen(function (w) {
|
nThen(function (w) {
|
||||||
// getIndex (and therefore the latest metadata)
|
// getIndex (and therefore the latest metadata)
|
||||||
getIndex(ctx, channel.id, w(function (err, index) {
|
getIndex(channel.id, w(function (err, index) {
|
||||||
if (err) {
|
if (err) {
|
||||||
w.abort();
|
w.abort();
|
||||||
return void Log.error('CHANNEL_MESSAGE_ERROR', err);
|
return void Log.error('CHANNEL_MESSAGE_ERROR', err);
|
||||||
|
@ -429,7 +420,7 @@ module.exports.create = function (cfg) {
|
||||||
metadata = index.metadata;
|
metadata = index.metadata;
|
||||||
|
|
||||||
// don't write messages to expired channels
|
// don't write messages to expired channels
|
||||||
if (checkExpired(ctx, channel)) { return void w.abort(); }
|
if (checkExpired(Server, channel)) { return void w.abort(); }
|
||||||
|
|
||||||
// if there's no validateKey present skip to the next block
|
// if there's no validateKey present skip to the next block
|
||||||
if (!metadata.validateKey) { return; }
|
if (!metadata.validateKey) { return; }
|
||||||
|
@ -479,20 +470,10 @@ module.exports.create = function (cfg) {
|
||||||
msgStruct.push(now());
|
msgStruct.push(now());
|
||||||
|
|
||||||
// storeMessage
|
// storeMessage
|
||||||
storeMessage(ctx, channel, JSON.stringify(msgStruct), isCp, getHash(msgStruct[4], Log));
|
storeMessage(channel, JSON.stringify(msgStruct), isCp, getHash(msgStruct[4], Log));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/* dropChannel
|
|
||||||
* exported as API
|
|
||||||
* used by chainpad-server/NetfluxWebsocketSrv.js
|
|
||||||
* cleans up memory structures which are managed entirely by the historyKeeper
|
|
||||||
* the netflux server manages other memory in ctx.channels
|
|
||||||
*/
|
|
||||||
const dropChannel = function (chanName) {
|
|
||||||
delete metadata_cache[chanName];
|
|
||||||
};
|
|
||||||
|
|
||||||
/* getHistoryOffset
|
/* getHistoryOffset
|
||||||
returns a number representing the byte offset from the start of the log
|
returns a number representing the byte offset from the start of the log
|
||||||
for whatever history you're seeking.
|
for whatever history you're seeking.
|
||||||
|
@ -522,12 +503,12 @@ module.exports.create = function (cfg) {
|
||||||
* -1 if you didn't find it
|
* -1 if you didn't find it
|
||||||
|
|
||||||
*/
|
*/
|
||||||
const getHistoryOffset = (ctx, channelName, lastKnownHash, cb /*:(e:?Error, os:?number)=>void*/) => {
|
const getHistoryOffset = (channelName, lastKnownHash, cb) => {
|
||||||
// lastKnownhash === -1 means we want the complete history
|
// lastKnownhash === -1 means we want the complete history
|
||||||
if (lastKnownHash === -1) { return void cb(null, 0); }
|
if (lastKnownHash === -1) { return void cb(null, 0); }
|
||||||
let offset = -1;
|
let offset = -1;
|
||||||
nThen((waitFor) => {
|
nThen((waitFor) => {
|
||||||
getIndex(ctx, channelName, waitFor((err, index) => {
|
getIndex(channelName, waitFor((err, index) => {
|
||||||
if (err) { waitFor.abort(); return void cb(err); }
|
if (err) { waitFor.abort(); return void cb(err); }
|
||||||
|
|
||||||
// check if the "hash" the client is requesting exists in the index
|
// check if the "hash" the client is requesting exists in the index
|
||||||
|
@ -600,10 +581,10 @@ module.exports.create = function (cfg) {
|
||||||
* GET_HISTORY
|
* GET_HISTORY
|
||||||
|
|
||||||
*/
|
*/
|
||||||
const getHistoryAsync = (ctx, channelName, lastKnownHash, beforeHash, handler, cb) => {
|
const getHistoryAsync = (channelName, lastKnownHash, beforeHash, handler, cb) => {
|
||||||
let offset = -1;
|
let offset = -1;
|
||||||
nThen((waitFor) => {
|
nThen((waitFor) => {
|
||||||
getHistoryOffset(ctx, channelName, lastKnownHash, waitFor((err, os) => {
|
getHistoryOffset(channelName, lastKnownHash, waitFor((err, os) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
waitFor.abort();
|
waitFor.abort();
|
||||||
return void cb(err);
|
return void cb(err);
|
||||||
|
@ -666,42 +647,50 @@ module.exports.create = function (cfg) {
|
||||||
/* onChannelCleared
|
/* onChannelCleared
|
||||||
* broadcasts to all clients in a channel if that channel is deleted
|
* broadcasts to all clients in a channel if that channel is deleted
|
||||||
*/
|
*/
|
||||||
const onChannelCleared = function (ctx, channel) {
|
const onChannelCleared = function (Server, channel) {
|
||||||
historyKeeperBroadcast(ctx, channel, {
|
Server.channelBroadcast(channel, {
|
||||||
error: 'ECLEARED',
|
error: 'ECLEARED',
|
||||||
channel: channel
|
channel: channel
|
||||||
});
|
}, HISTORY_KEEPER_ID);
|
||||||
};
|
};
|
||||||
|
|
||||||
// When a channel is removed from datastore, broadcast a message to all its connected users
|
// When a channel is removed from datastore, broadcast a message to all its connected users
|
||||||
const onChannelDeleted = function (ctx, channel) {
|
const onChannelDeleted = function (Server, channel) {
|
||||||
store.closeChannel(channel, function () {
|
store.closeChannel(channel, function () {
|
||||||
historyKeeperBroadcast(ctx, channel, {
|
Server.channelBroadcast(channel, {
|
||||||
error: 'EDELETED',
|
error: 'EDELETED',
|
||||||
channel: channel
|
channel: channel
|
||||||
});
|
}, HISTORY_KEEPER_ID);
|
||||||
});
|
});
|
||||||
delete ctx.channels[channel];
|
|
||||||
|
delete channel_cache[channel];
|
||||||
|
Server.clearChannel(channel);
|
||||||
delete metadata_cache[channel];
|
delete metadata_cache[channel];
|
||||||
};
|
};
|
||||||
// Check if the selected channel is expired
|
// Check if the selected channel is expired
|
||||||
// If it is, remove it from memory and broadcast a message to its members
|
// If it is, remove it from memory and broadcast a message to its members
|
||||||
|
|
||||||
const onChannelMetadataChanged = function (ctx, channel, metadata) {
|
const onChannelMetadataChanged = function (Server, channel, metadata) {
|
||||||
if (channel && metadata_cache[channel] && typeof (metadata) === "object") {
|
if (!(channel && metadata_cache[channel] && typeof (metadata) === "object")) { return; }
|
||||||
Log.silly('SET_METADATA_CACHE', 'Channel '+ channel +', metadata: '+ JSON.stringify(metadata));
|
Log.silly('SET_METADATA_CACHE', {
|
||||||
metadata_cache[channel] = metadata;
|
channel: channel,
|
||||||
if (ctx.channels[channel] && ctx.channels[channel].index) {
|
metadata: JSON.stringify(metadata),
|
||||||
ctx.channels[channel].index.metadata = metadata;
|
});
|
||||||
}
|
|
||||||
historyKeeperBroadcast(ctx, channel, metadata);
|
metadata_cache[channel] = metadata;
|
||||||
|
|
||||||
|
if (channel_cache[channel] && channel_cache[channel].index) {
|
||||||
|
channel_cache[channel].index.metadata = metadata;
|
||||||
}
|
}
|
||||||
|
Server.channelBroadcast(channel, metadata, HISTORY_KEEPER_ID);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGetHistory = function (ctx, seq, user, parsed) {
|
const handleGetHistory = function (Server, seq, user, parsed) {
|
||||||
// parsed[1] is the channel id
|
// parsed[1] is the channel id
|
||||||
// parsed[2] is a validation key or an object containing metadata (optionnal)
|
// parsed[2] is a validation key or an object containing metadata (optionnal)
|
||||||
// parsed[3] is the last known hash (optionnal)
|
// parsed[3] is the last known hash (optionnal)
|
||||||
ctx.sendMsg(ctx, user, [seq, 'ACK']);
|
|
||||||
|
Server.send(user.id, [seq, 'ACK']);
|
||||||
var channelName = parsed[1];
|
var channelName = parsed[1];
|
||||||
var config = parsed[2];
|
var config = parsed[2];
|
||||||
var metadata = {};
|
var metadata = {};
|
||||||
|
@ -736,7 +725,7 @@ module.exports.create = function (cfg) {
|
||||||
unfortunately, we can't just serve it blindly, since then young channels will
|
unfortunately, we can't just serve it blindly, since then young channels will
|
||||||
send the metadata twice, so let's do a quick check of what we're going to serve...
|
send the metadata twice, so let's do a quick check of what we're going to serve...
|
||||||
*/
|
*/
|
||||||
getIndex(ctx, channelName, waitFor((err, index) => {
|
getIndex(channelName, waitFor((err, index) => {
|
||||||
/* if there's an error here, it should be encountered
|
/* if there's an error here, it should be encountered
|
||||||
and handled by the next nThen block.
|
and handled by the next nThen block.
|
||||||
so, let's just fall through...
|
so, let's just fall through...
|
||||||
|
@ -750,29 +739,29 @@ module.exports.create = function (cfg) {
|
||||||
if (!index || !index.metadata) { return void w(); }
|
if (!index || !index.metadata) { return void w(); }
|
||||||
// And then check if the channel is expired. If it is, send the error and abort
|
// And then check if the channel is expired. If it is, send the error and abort
|
||||||
// FIXME this is hard to read because 'checkExpired' has side effects
|
// FIXME this is hard to read because 'checkExpired' has side effects
|
||||||
if (checkExpired(ctx, channelName)) { return void waitFor.abort(); }
|
if (checkExpired(Server, channelName)) { return void waitFor.abort(); }
|
||||||
// always send metadata with GET_HISTORY requests
|
// always send metadata with GET_HISTORY requests
|
||||||
ctx.sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(index.metadata)], w);
|
Server.send(user.id, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(index.metadata)], w);
|
||||||
}));
|
}));
|
||||||
}).nThen(() => {
|
}).nThen(() => {
|
||||||
let msgCount = 0;
|
let msgCount = 0;
|
||||||
|
|
||||||
// TODO compute lastKnownHash in a manner such that it will always skip past the metadata line?
|
// TODO compute lastKnownHash in a manner such that it will always skip past the metadata line?
|
||||||
getHistoryAsync(ctx, channelName, lastKnownHash, false, (msg, readMore) => {
|
getHistoryAsync(channelName, lastKnownHash, false, (msg, readMore) => {
|
||||||
if (!msg) { return; }
|
if (!msg) { return; }
|
||||||
msgCount++;
|
msgCount++;
|
||||||
// avoid sending the metadata message a second time
|
// avoid sending the metadata message a second time
|
||||||
if (isMetadataMessage(msg) && metadata_cache[channelName]) { return readMore(); }
|
if (isMetadataMessage(msg) && metadata_cache[channelName]) { return readMore(); }
|
||||||
ctx.sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(msg)], readMore);
|
Server.send(user.id, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(msg)], readMore);
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
if (err && err.code !== 'ENOENT') {
|
if (err && err.code !== 'ENOENT') {
|
||||||
if (err.message !== 'EINVAL') { Log.error("HK_GET_HISTORY", err); }
|
if (err.message !== 'EINVAL') { Log.error("HK_GET_HISTORY", err); }
|
||||||
const parsedMsg = {error:err.message, channel: channelName};
|
const parsedMsg = {error:err.message, channel: channelName};
|
||||||
ctx.sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(parsedMsg)]);
|
Server.send(user.id, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(parsedMsg)]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const chan = ctx.channels[channelName];
|
const chan = channel_cache[channelName];
|
||||||
|
|
||||||
if (msgCount === 0 && !metadata_cache[channelName] && chan && chan.indexOf(user) > -1) {
|
if (msgCount === 0 && !metadata_cache[channelName] && chan && chan.indexOf(user) > -1) {
|
||||||
metadata_cache[channelName] = metadata;
|
metadata_cache[channelName] = metadata;
|
||||||
|
@ -811,21 +800,22 @@ module.exports.create = function (cfg) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
ctx.sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(metadata)]);
|
Server.send(user.id, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(metadata)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// End of history message:
|
// End of history message:
|
||||||
let parsedMsg = {state: 1, channel: channelName};
|
let parsedMsg = {state: 1, channel: channelName};
|
||||||
ctx.sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(parsedMsg)]);
|
|
||||||
|
Server.send(user.id, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(parsedMsg)]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGetHistoryRange = function (ctx, seq, user, parsed) {
|
const handleGetHistoryRange = function (Server, seq, user, parsed) {
|
||||||
var channelName = parsed[1];
|
var channelName = parsed[1];
|
||||||
var map = parsed[2];
|
var map = parsed[2];
|
||||||
if (!(map && typeof(map) === 'object')) {
|
if (!(map && typeof(map) === 'object')) {
|
||||||
return void ctx.sendMsg(ctx, user, [seq, 'ERROR', 'INVALID_ARGS', HISTORY_KEEPER_ID]);
|
return void Server.send(user.id, [seq, 'ERROR', 'INVALID_ARGS', HISTORY_KEEPER_ID]);
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldestKnownHash = map.from;
|
var oldestKnownHash = map.from;
|
||||||
|
@ -833,14 +823,14 @@ module.exports.create = function (cfg) {
|
||||||
var desiredCheckpoint = map.cpCount;
|
var desiredCheckpoint = map.cpCount;
|
||||||
var txid = map.txid;
|
var txid = map.txid;
|
||||||
if (typeof(desiredMessages) !== 'number' && typeof(desiredCheckpoint) !== 'number') {
|
if (typeof(desiredMessages) !== 'number' && typeof(desiredCheckpoint) !== 'number') {
|
||||||
return void ctx.sendMsg(ctx, user, [seq, 'ERROR', 'UNSPECIFIED_COUNT', HISTORY_KEEPER_ID]);
|
return void Server.send(user.id, [seq, 'ERROR', 'UNSPECIFIED_COUNT', HISTORY_KEEPER_ID]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!txid) {
|
if (!txid) {
|
||||||
return void ctx.sendMsg(ctx, user, [seq, 'ERROR', 'NO_TXID', HISTORY_KEEPER_ID]);
|
return void Server.send(user.id, [seq, 'ERROR', 'NO_TXID', HISTORY_KEEPER_ID]);
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.sendMsg(ctx, user, [seq, 'ACK']);
|
Server.send(user.id, [seq, 'ACK']);
|
||||||
return void getOlderHistory(channelName, oldestKnownHash, function (messages) {
|
return void getOlderHistory(channelName, oldestKnownHash, function (messages) {
|
||||||
var toSend = [];
|
var toSend = [];
|
||||||
if (typeof (desiredMessages) === "number") {
|
if (typeof (desiredMessages) === "number") {
|
||||||
|
@ -856,64 +846,66 @@ module.exports.create = function (cfg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toSend.forEach(function (msg) {
|
toSend.forEach(function (msg) {
|
||||||
ctx.sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id,
|
Server.send(user.id, [0, HISTORY_KEEPER_ID, 'MSG', user.id,
|
||||||
JSON.stringify(['HISTORY_RANGE', txid, msg])]);
|
JSON.stringify(['HISTORY_RANGE', txid, msg])]);
|
||||||
});
|
});
|
||||||
|
|
||||||
ctx.sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id,
|
Server.send(user.id, [0, HISTORY_KEEPER_ID, 'MSG', user.id,
|
||||||
JSON.stringify(['HISTORY_RANGE_END', txid, channelName])
|
JSON.stringify(['HISTORY_RANGE_END', txid, channelName])
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGetFullHistory = function (ctx, seq, user, parsed) {
|
const handleGetFullHistory = function (Server, seq, user, parsed) {
|
||||||
// parsed[1] is the channel id
|
// parsed[1] is the channel id
|
||||||
// parsed[2] is a validation key (optionnal)
|
// parsed[2] is a validation key (optionnal)
|
||||||
// parsed[3] is the last known hash (optionnal)
|
// parsed[3] is the last known hash (optionnal)
|
||||||
ctx.sendMsg(ctx, user, [seq, 'ACK']);
|
|
||||||
|
Server.send(user.id, [seq, 'ACK']);
|
||||||
|
|
||||||
// FIXME should we send metadata here too?
|
// FIXME should we send metadata here too?
|
||||||
// none of the clientside code which uses this API needs metadata, but it won't hurt to send it (2019-08-22)
|
// none of the clientside code which uses this API needs metadata, but it won't hurt to send it (2019-08-22)
|
||||||
return void getHistoryAsync(ctx, parsed[1], -1, false, (msg, readMore) => {
|
return void getHistoryAsync(parsed[1], -1, false, (msg, readMore) => {
|
||||||
if (!msg) { return; }
|
if (!msg) { return; }
|
||||||
ctx.sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(['FULL_HISTORY', msg])], readMore);
|
Server.send(user.id, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(['FULL_HISTORY', msg])], readMore);
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
let parsedMsg = ['FULL_HISTORY_END', parsed[1]];
|
let parsedMsg = ['FULL_HISTORY_END', parsed[1]];
|
||||||
if (err) {
|
if (err) {
|
||||||
Log.error('HK_GET_FULL_HISTORY', err.stack);
|
Log.error('HK_GET_FULL_HISTORY', err.stack);
|
||||||
parsedMsg = ['ERROR', parsed[1], err.message];
|
parsedMsg = ['ERROR', parsed[1], err.message];
|
||||||
}
|
}
|
||||||
ctx.sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(parsedMsg)]);
|
Server.send(user.id, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify(parsedMsg)]);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRPC = function (ctx, seq, user, parsed) {
|
const handleRPC = function (Server, seq, user, parsed) {
|
||||||
if (typeof(rpc) !== 'function') { return; }
|
if (typeof(rpc) !== 'function') { return; }
|
||||||
|
|
||||||
/* RPC Calls... */
|
/* RPC Calls... */
|
||||||
var rpc_call = parsed.slice(1);
|
var rpc_call = parsed.slice(1);
|
||||||
|
|
||||||
ctx.sendMsg(ctx, user, [seq, 'ACK']);
|
// XXX ensure user is guaranteed to have 'id'
|
||||||
|
Server.send(user.id, [seq, 'ACK']);
|
||||||
try {
|
try {
|
||||||
// slice off the sequence number and pass in the rest of the message
|
// slice off the sequence number and pass in the rest of the message
|
||||||
rpc(ctx, rpc_call, function (err, output) {
|
rpc(Server, rpc_call, function (err, output) {
|
||||||
if (err) {
|
if (err) {
|
||||||
ctx.sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify([parsed[0], 'ERROR', err])]);
|
Server.send(user.id, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify([parsed[0], 'ERROR', err])]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var msg = rpc_call[0].slice();
|
var msg = rpc_call[0].slice();
|
||||||
if (msg[3] === 'REMOVE_OWNED_CHANNEL') {
|
if (msg[3] === 'REMOVE_OWNED_CHANNEL') {
|
||||||
onChannelDeleted(ctx, msg[4]);
|
onChannelDeleted(Server, msg[4]);
|
||||||
}
|
}
|
||||||
if (msg[3] === 'CLEAR_OWNED_CHANNEL') {
|
if (msg[3] === 'CLEAR_OWNED_CHANNEL') {
|
||||||
onChannelCleared(ctx, msg[4]);
|
onChannelCleared(Server, msg[4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg[3] === 'SET_METADATA') { // or whatever we call the RPC????
|
if (msg[3] === 'SET_METADATA') { // or whatever we call the RPC????
|
||||||
// make sure we update our cache of metadata
|
// make sure we update our cache of metadata
|
||||||
// or at least invalidate it and force other mechanisms to recompute its state
|
// or at least invalidate it and force other mechanisms to recompute its state
|
||||||
// 'output' could be the new state as computed by rpc
|
// 'output' could be the new state as computed by rpc
|
||||||
onChannelMetadataChanged(ctx, msg[4].channel, output[1]);
|
onChannelMetadataChanged(Server, msg[4].channel, output[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// unauthenticated RPC calls have a different message format
|
// unauthenticated RPC calls have a different message format
|
||||||
|
@ -921,10 +913,10 @@ module.exports.create = function (cfg) {
|
||||||
// this is an inline reimplementation of historyKeeperBroadcast
|
// this is an inline reimplementation of historyKeeperBroadcast
|
||||||
// because if we use that directly it will bypass signature validation
|
// because if we use that directly it will bypass signature validation
|
||||||
// which opens up the user to malicious behaviour
|
// which opens up the user to malicious behaviour
|
||||||
let chan = ctx.channels[output.channel];
|
let chan = channel_cache[output.channel];
|
||||||
if (chan && chan.length) {
|
if (chan && chan.length) {
|
||||||
chan.forEach(function (user) {
|
chan.forEach(function (user) {
|
||||||
ctx.sendMsg(ctx, user, output.message);
|
Server.send(user.id, output.message);
|
||||||
//[0, null, 'MSG', user.id, JSON.stringify(output.message)]);
|
//[0, null, 'MSG', user.id, JSON.stringify(output.message)]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -934,10 +926,10 @@ module.exports.create = function (cfg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// finally, send a response to the client that sent the RPC
|
// finally, send a response to the client that sent the RPC
|
||||||
ctx.sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify([parsed[0]].concat(output))]);
|
Server.send(user.id, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify([parsed[0]].concat(output))]);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ctx.sendMsg(ctx, user, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify([parsed[0], 'ERROR', 'SERVER_ERROR'])]);
|
Server.send(user.id, [0, HISTORY_KEEPER_ID, 'MSG', user.id, JSON.stringify([parsed[0], 'ERROR', 'SERVER_ERROR'])]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -953,7 +945,7 @@ module.exports.create = function (cfg) {
|
||||||
* check if it's expired and execute all the associated side-effects
|
* check if it's expired and execute all the associated side-effects
|
||||||
* routes queries to the appropriate handlers
|
* routes queries to the appropriate handlers
|
||||||
*/
|
*/
|
||||||
const onDirectMessage = function (ctx, seq, user, json) {
|
const onDirectMessage = function (Server, seq, user, json) {
|
||||||
Log.silly('HK_MESSAGE', json);
|
Log.silly('HK_MESSAGE', json);
|
||||||
|
|
||||||
let parsed;
|
let parsed;
|
||||||
|
@ -967,33 +959,49 @@ module.exports.create = function (cfg) {
|
||||||
// If the requested history is for an expired channel, abort
|
// If the requested history is for an expired channel, abort
|
||||||
// Note the if we don't have the keys for that channel in metadata_cache, we'll
|
// Note the if we don't have the keys for that channel in metadata_cache, we'll
|
||||||
// have to abort later (once we know the expiration time)
|
// have to abort later (once we know the expiration time)
|
||||||
if (checkExpired(ctx, parsed[1])) { return; }
|
if (checkExpired(Server, parsed[1])) { return; }
|
||||||
|
|
||||||
// look up the appropriate command in the map of commands or fall back to RPC
|
// look up the appropriate command in the map of commands or fall back to RPC
|
||||||
var command = directMessageCommands[parsed[0]] || handleRPC;
|
var command = directMessageCommands[parsed[0]] || handleRPC;
|
||||||
|
|
||||||
// run the command with the standard function signature
|
// run the command with the standard function signature
|
||||||
command(ctx, seq, user, parsed);
|
command(Server, seq, user, parsed);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
cfg.historyKeeper = {
|
||||||
id: HISTORY_KEEPER_ID,
|
id: HISTORY_KEEPER_ID,
|
||||||
channelMessage: function (ctx, channel, msgStruct) {
|
|
||||||
onChannelMessage(ctx, channel, msgStruct);
|
channelMessage: function (Server, channel, msgStruct) {
|
||||||
|
// netflux-server emits 'channelMessage' events whenever someone broadcasts to a channel
|
||||||
|
// historyKeeper stores these messages if the channel id indicates that they are
|
||||||
|
// a channel type with permanent history
|
||||||
|
onChannelMessage(Server, channel, msgStruct);
|
||||||
},
|
},
|
||||||
channelClose: function (channelName) {
|
channelClose: function (channelName) {
|
||||||
|
// netflux-server emits 'channelClose' events whenever everyone leaves a channel
|
||||||
|
// we drop cached metadata and indexes at the same time
|
||||||
dropChannel(channelName);
|
dropChannel(channelName);
|
||||||
},
|
},
|
||||||
channelOpen: function (ctx, channelName, user) {
|
channelOpen: function (Server, channelName, userId) {
|
||||||
ctx.sendMsg(ctx, user, [
|
channel_cache[channelName] = {};
|
||||||
|
Server.send(userId, [
|
||||||
0,
|
0,
|
||||||
HISTORY_KEEPER_ID, // ctx.historyKeeper.id
|
HISTORY_KEEPER_ID,
|
||||||
'JOIN',
|
'JOIN',
|
||||||
channelName
|
channelName
|
||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
directMessage: function (ctx, seq, user, json) {
|
directMessage: function (Server, seq, user, json) {
|
||||||
onDirectMessage(ctx, seq, user, json);
|
// netflux-server allows you to register an id with a handler
|
||||||
|
// this handler is invoked every time someone sends a message to that id
|
||||||
|
onDirectMessage(Server, seq, user, json);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
RPC.create(cfg, function (err, _rpc) {
|
||||||
|
if (err) { throw err; }
|
||||||
|
|
||||||
|
rpc = _rpc;
|
||||||
|
cb(void 0, cfg.historyKeeper);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
38
lib/rpc.js
38
lib/rpc.js
|
@ -24,7 +24,6 @@ const UNAUTHENTICATED_CALLS = [
|
||||||
'GET_MULTIPLE_FILE_SIZE',
|
'GET_MULTIPLE_FILE_SIZE',
|
||||||
'IS_CHANNEL_PINNED',
|
'IS_CHANNEL_PINNED',
|
||||||
'IS_NEW_CHANNEL',
|
'IS_NEW_CHANNEL',
|
||||||
'GET_HISTORY_OFFSET',
|
|
||||||
'GET_DELETED_PADS',
|
'GET_DELETED_PADS',
|
||||||
'WRITE_PRIVATE_MESSAGE',
|
'WRITE_PRIVATE_MESSAGE',
|
||||||
];
|
];
|
||||||
|
@ -66,25 +65,9 @@ var isUnauthenticateMessage = function (msg) {
|
||||||
return msg && msg.length === 2 && isUnauthenticatedCall(msg[0]);
|
return msg && msg.length === 2 && isUnauthenticatedCall(msg[0]);
|
||||||
};
|
};
|
||||||
|
|
||||||
var handleUnauthenticatedMessage = function (Env, msg, respond, nfwssCtx) {
|
var handleUnauthenticatedMessage = function (Env, msg, respond, Server) {
|
||||||
Env.Log.silly('LOG_RPC', msg[0]);
|
Env.Log.silly('LOG_RPC', msg[0]);
|
||||||
switch (msg[0]) {
|
switch (msg[0]) {
|
||||||
case 'GET_HISTORY_OFFSET': { // XXX not actually used anywhere?
|
|
||||||
if (typeof(msg[1]) !== 'object' || typeof(msg[1].channelName) !== 'string') {
|
|
||||||
return respond('INVALID_ARG_FORMAT', msg);
|
|
||||||
}
|
|
||||||
const msgHash = typeof(msg[1].msgHash) === 'string' ? msg[1].msgHash : undefined;
|
|
||||||
nfwssCtx.getHistoryOffset(nfwssCtx, msg[1].channelName, msgHash, (e, ret) => {
|
|
||||||
if (e) {
|
|
||||||
if (e.code !== 'ENOENT') {
|
|
||||||
Env.WARN(e.stack, msg);
|
|
||||||
}
|
|
||||||
return respond(e.message);
|
|
||||||
}
|
|
||||||
respond(e, [null, ret, null]);
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'GET_FILE_SIZE':
|
case 'GET_FILE_SIZE':
|
||||||
return void Pinning.getFileSize(Env, msg[1], function (e, size) {
|
return void Pinning.getFileSize(Env, msg[1], function (e, size) {
|
||||||
Env.WARN(e, msg[1]);
|
Env.WARN(e, msg[1]);
|
||||||
|
@ -120,7 +103,7 @@ var handleUnauthenticatedMessage = function (Env, msg, respond, nfwssCtx) {
|
||||||
respond(e, [null, isNew, null]);
|
respond(e, [null, isNew, null]);
|
||||||
});
|
});
|
||||||
case 'WRITE_PRIVATE_MESSAGE':
|
case 'WRITE_PRIVATE_MESSAGE':
|
||||||
return void Channel.writePrivateMessage(Env, msg[1], nfwssCtx, function (e, output) {
|
return void Channel.writePrivateMessage(Env, msg[1], Server, function (e, output) {
|
||||||
respond(e, output);
|
respond(e, output);
|
||||||
});
|
});
|
||||||
default:
|
default:
|
||||||
|
@ -134,7 +117,7 @@ var handleAuthenticatedMessage = function (Env, map) {
|
||||||
var safeKey = map.safeKey;
|
var safeKey = map.safeKey;
|
||||||
var publicKey = map.publicKey;
|
var publicKey = map.publicKey;
|
||||||
var Respond = map.Respond;
|
var Respond = map.Respond;
|
||||||
var ctx = map.ctx;
|
var Server = map.Server;
|
||||||
|
|
||||||
Env.Log.silly('LOG_RPC', msg[0]);
|
Env.Log.silly('LOG_RPC', msg[0]);
|
||||||
switch (msg[0]) {
|
switch (msg[0]) {
|
||||||
|
@ -265,7 +248,7 @@ var handleAuthenticatedMessage = function (Env, map) {
|
||||||
Respond(e);
|
Respond(e);
|
||||||
});
|
});
|
||||||
case 'ADMIN':
|
case 'ADMIN':
|
||||||
return void Admin.command(Env, ctx, safeKey, msg[1], function (e, result) { // XXX SPECIAL
|
return void Admin.command(Env, Server, safeKey, msg[1], function (e, result) { // XXX SPECIAL
|
||||||
if (e) {
|
if (e) {
|
||||||
Env.WARN(e, result);
|
Env.WARN(e, result);
|
||||||
return void Respond(e);
|
return void Respond(e);
|
||||||
|
@ -285,7 +268,7 @@ var handleAuthenticatedMessage = function (Env, map) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var rpc = function (Env, ctx, data, respond) {
|
var rpc = function (Env, Server, data, respond) {
|
||||||
if (!Array.isArray(data)) {
|
if (!Array.isArray(data)) {
|
||||||
Env.Log.debug('INVALID_ARG_FORMET', data);
|
Env.Log.debug('INVALID_ARG_FORMET', data);
|
||||||
return void respond('INVALID_ARG_FORMAT');
|
return void respond('INVALID_ARG_FORMAT');
|
||||||
|
@ -304,7 +287,7 @@ var rpc = function (Env, ctx, data, respond) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUnauthenticateMessage(msg)) {
|
if (isUnauthenticateMessage(msg)) {
|
||||||
return handleUnauthenticatedMessage(Env, msg, respond, ctx);
|
return handleUnauthenticatedMessage(Env, msg, respond);
|
||||||
}
|
}
|
||||||
|
|
||||||
var signature = msg.shift();
|
var signature = msg.shift();
|
||||||
|
@ -369,7 +352,7 @@ var rpc = function (Env, ctx, data, respond) {
|
||||||
safeKey: safeKey,
|
safeKey: safeKey,
|
||||||
publicKey: publicKey,
|
publicKey: publicKey,
|
||||||
Respond: Respond,
|
Respond: Respond,
|
||||||
ctx: ctx,
|
Server: Server,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -394,6 +377,7 @@ RPC.create = function (config, cb) {
|
||||||
};
|
};
|
||||||
|
|
||||||
var Env = {
|
var Env = {
|
||||||
|
historyKeeper: config.historyKeeper,
|
||||||
defaultStorageLimit: config.defaultStorageLimit,
|
defaultStorageLimit: config.defaultStorageLimit,
|
||||||
maxUploadSize: config.maxUploadSize || (20 * 1024 * 1024),
|
maxUploadSize: config.maxUploadSize || (20 * 1024 * 1024),
|
||||||
Sessions: {},
|
Sessions: {},
|
||||||
|
@ -465,11 +449,9 @@ RPC.create = function (config, cb) {
|
||||||
Env.blobStore = blob;
|
Env.blobStore = blob;
|
||||||
}));
|
}));
|
||||||
}).nThen(function () {
|
}).nThen(function () {
|
||||||
// XXX it's ugly that we pass ctx and Env separately
|
cb(void 0, function (Server, data, respond) {
|
||||||
// when they're effectively the same thing...
|
|
||||||
cb(void 0, function (ctx, data, respond) {
|
|
||||||
try {
|
try {
|
||||||
return rpc(Env, ctx, data, respond);
|
return rpc(Env, Server, data, respond);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Error from RPC with data " + JSON.stringify(data));
|
console.log("Error from RPC with data " + JSON.stringify(data));
|
||||||
console.log(e.stack);
|
console.log(e.stack);
|
||||||
|
|
Loading…
Reference in New Issue