feat: add message storage and fetching api #21

Merged
hippoz merged 4 commits from message-history into master 2021-10-03 21:20:29 +03:00
3 changed files with 37 additions and 19 deletions
Showing only changes of commit 1008e68d54 - Show all commits

View file

@ -94,6 +94,16 @@ app.post("/post/create", [
app.get("/channel/:channel/messages", [ app.get("/channel/:channel/messages", [
param("channel").not().isEmpty().trim().escape().isLength({ min: 24, max: 24 }) param("channel").not().isEmpty().trim().escape().isLength({ min: 24, max: 24 })
], authenticateEndpoint(async (req, res) => { ], authenticateEndpoint(async (req, res) => {
if (!config.policies.allowSavingMessages) {
// TODO: hack
res.status(200).json({
error: false,
message: "SUCCESS_CHANNEL_MESSAGES_FETCHED",
channelMessages: []
});
return;
}
const errors = validationResult(req); const errors = validationResult(req);
if (!errors.isEmpty()) { if (!errors.isEmpty()) {
res.status(400).json({ error: true, message: "ERROR_REQUEST_INVALID_DATA", errors: errors.array() }); res.status(400).json({ error: true, message: "ERROR_REQUEST_INVALID_DATA", errors: errors.array() });

View file

@ -1,5 +1,6 @@
const websockets = require("ws"); const websockets = require("ws");
const uuid = require("uuid"); const { v4 } = require("uuid");
const mongoose = require("mongoose");
const { policies, gatewayPingInterval, gatewayPingCheckInterval, clientFacingPingInterval } = require("../../../config"); const { policies, gatewayPingInterval, gatewayPingCheckInterval, clientFacingPingInterval } = require("../../../config");
const { experiments } = require("../../../experiments"); const { experiments } = require("../../../experiments");
@ -18,21 +19,21 @@ const wsCloseCodes = {
NOT_AUTHORIZED: [4006, "Not authorized"], NOT_AUTHORIZED: [4006, "Not authorized"],
FLOODING: [4007, "Flooding"], FLOODING: [4007, "Flooding"],
NO_PING: [4008, "No ping"], NO_PING: [4008, "No ping"],
UNSUPPORTED_ATTRIBUTE: [4009, "Unsupported attribute."],
}; };
const attributes = { const attributes = {
PRESENCE_UPDATES: "PRESENCE_UPDATES", PRESENCE_UPDATES: "PRESENCE_UPDATES",
SAVE_MESSAGES: "SAVE_MESSAGES"
}; };
const supportedAttributes = [attributes.PRESENCE_UPDATES, attributes.SAVE_MESSAGES]; const supportedAttributes = [attributes.PRESENCE_UPDATES];
class GatewaySession { class GatewaySession {
constructor() { constructor() {
this.authenticated = false; this.authenticated = false;
this.user = null; this.user = null;
this.token = null; this.token = null;
this.sessionId = uuid.v4(); this.sessionId = v4();
this.attributes = []; this.attributes = [];
// Specific to websocket sessions // Specific to websocket sessions
@ -110,7 +111,7 @@ class GatewayHandler {
const session = new GatewaySession(); const session = new GatewaySession();
session.setWebsocketClient(ws); session.setWebsocketClient(ws);
session.send("HELLO", { pingInterval: clientFacingPingInterval }); session.send("HELLO", { pingInterval: clientFacingPingInterval, supportedAttributes });
return session; return session;
} }
@ -177,8 +178,9 @@ class GatewayHandler {
if (data.attributes) { if (data.attributes) {
if (!Array.isArray(data.attributes) || data.attributes.length > 8) return {error: wsCloseCodes.PAYLOAD_ERROR}; if (!Array.isArray(data.attributes) || data.attributes.length > 8) return {error: wsCloseCodes.PAYLOAD_ERROR};
for (let i = 0; i < data.attributes; i++) { for (let i = 0; i < data.attributes.length; i++) {
if (!supportedAttributes.includes(data[i])) return {error: wsCloseCodes.PAYLOAD_ERROR}; if (!supportedAttributes.includes(data.attributes[i]))
return {error: wsCloseCodes.UNSUPPORTED_ATTRIBUTE};
} }
session.attributes = data.attributes; session.attributes = data.attributes;
} }
@ -232,7 +234,20 @@ class GatewayHandler {
// Check if the user is in that channel before broadcasting the message // Check if the user is in that channel before broadcasting the message
if (!session.channels.includes(data.channel._id)) return {error: wsCloseCodes.NOT_AUTHORIZED}; if (!session.channels.includes(data.channel._id)) return {error: wsCloseCodes.NOT_AUTHORIZED};
this.eachInChannel({channelId: data.channel._id}, ({ session: remoteSession }) => { this.eachInChannel({channelId: data.channel._id}, async ({ session: remoteSession }) => {
let id;
if (policies.allowSavingMessages) {
const message = await Message.create({
author: session.user._id,
channel: data.channel._id,
content: messageContent,
createdAt: new Date().getTime()
});
id = message._id;
} else {
id = new mongoose.Types.ObjectId();
}
remoteSession.send("EVENT_CREATE_MESSAGE", { remoteSession.send("EVENT_CREATE_MESSAGE", {
content: messageContent, content: messageContent,
channel: { channel: {
@ -242,18 +257,9 @@ class GatewayHandler {
_id: session.user._id, _id: session.user._id,
username: session.user.username username: session.user.username
}, },
_id: uuid.v4() _id: id
}); });
}); });
if (session.hasAttribute(attributes.SAVE_MESSAGES)) {
await Message.create({
author: session.user._id,
channel: data.channel._id,
content: messageContent,
createdAt: new Date().getTime()
});
}
} }
} }

View file

@ -18,6 +18,9 @@ module.exports = {
allowAccountCreation: true, allowAccountCreation: true,
allowLogin: true, allowLogin: true,
allowGatewayConnection: true, allowGatewayConnection: true,
// The policy below will make all messages sent over the gateway to be in plain text saved to the database.
// This is experimental and dangerous, and, as such, should generally not be used.
allowSavingMessages: true,
perUserMaxGatewayConnections: 4 perUserMaxGatewayConnections: 4
}, },
/* /*
@ -35,7 +38,6 @@ module.exports = {
gatewayPingInterval: 15000, gatewayPingInterval: 15000,
gatewayPingCheckInterval: 4500, gatewayPingCheckInterval: 4500,
clientFacingPingInterval: 14750, clientFacingPingInterval: 14750,
unsafeStoreMessages: false,
bcryptRounds: 10, bcryptRounds: 10,
roleMap: { roleMap: {
"BANNED": 0, "BANNED": 0,